├── .github ├── dependabot.yml └── workflows │ ├── generated-pr.yml │ ├── js-test-and-release.yml │ ├── semantic-pull-request.yml │ └── stale.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── package.json ├── src └── index.ts ├── test ├── test-cid.spec.ts ├── test-multiaddr.spec.ts ├── test-multihash.spec.ts ├── test-path.spec.ts ├── test-subdomain.spec.ts └── test-url.spec.ts ├── tsconfig.json └── typedoc.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 20 9 | commit-message: 10 | prefix: "deps" 11 | prefix-development: "deps(dev)" 12 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/js-test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: test & maybe release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | id-token: write 13 | packages: write 14 | pull-requests: write 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | js-test-and-release: 22 | uses: ipdxco/unified-github-workflows/.github/workflows/js-test-and-release.yml@v1.0 23 | secrets: 24 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 25 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | UCI_GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN }} 28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Semantic PR 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | uses: pl-strflt/.github/.github/workflows/reusable-semantic-pull-request.yml@v0.3 13 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | .docs 5 | .coverage 6 | node_modules 7 | package-lock.json 8 | yarn.lock 9 | .vscode 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [8.0.4](https://github.com/ipfs-shipyard/is-ipfs/compare/v8.0.3...v8.0.4) (2024-02-06) 2 | 3 | 4 | ### Dependencies 5 | 6 | * bump multiformats from 11.0.2 to 13.0.1 ([#92](https://github.com/ipfs-shipyard/is-ipfs/issues/92)) ([552d1e6](https://github.com/ipfs-shipyard/is-ipfs/commit/552d1e6f89107776451e158b52e3e901c96be999)) 7 | * bump uint8arrays from 4.0.10 to 5.0.1 ([#93](https://github.com/ipfs-shipyard/is-ipfs/issues/93)) ([3cb3722](https://github.com/ipfs-shipyard/is-ipfs/commit/3cb3722972d8613234826d6848a878030b262e89)) 8 | 9 | ## [8.0.3](https://github.com/ipfs-shipyard/is-ipfs/compare/v8.0.2...v8.0.3) (2024-02-06) 10 | 11 | 12 | ### Dependencies 13 | 14 | * **dev:** bump aegir from 37.12.1 to 42.2.3 ([#90](https://github.com/ipfs-shipyard/is-ipfs/issues/90)) ([555e75f](https://github.com/ipfs-shipyard/is-ipfs/commit/555e75fdfafa094fd94fb2c6336f74c9b809facc)) 15 | 16 | ## [8.0.2](https://github.com/ipfs-shipyard/is-ipfs/compare/v8.0.1...v8.0.2) (2024-02-06) 17 | 18 | 19 | ### Trivial Changes 20 | 21 | * add or force update .github/workflows/js-test-and-release.yml ([#87](https://github.com/ipfs-shipyard/is-ipfs/issues/87)) ([a0c32d5](https://github.com/ipfs-shipyard/is-ipfs/commit/a0c32d5da7c586c373384d707a4f159621081c75)) 22 | * delete templates [skip ci] ([#86](https://github.com/ipfs-shipyard/is-ipfs/issues/86)) ([6bc0a79](https://github.com/ipfs-shipyard/is-ipfs/commit/6bc0a7943073b8d06f74569e9130148783c10fbe)) 23 | 24 | 25 | ### Dependencies 26 | 27 | * bump @multiformats/mafmt from 11.1.2 to 12.1.6 ([#91](https://github.com/ipfs-shipyard/is-ipfs/issues/91)) ([9be42f1](https://github.com/ipfs-shipyard/is-ipfs/commit/9be42f1c23c4861f545110b624c952b741f40ecf)) 28 | * bump @multiformats/multiaddr from 11.6.1 to 12.1.14 ([#89](https://github.com/ipfs-shipyard/is-ipfs/issues/89)) ([6b1a651](https://github.com/ipfs-shipyard/is-ipfs/commit/6b1a65187f02ce523ff41370edec748fa736fa56)) 29 | 30 | ## [8.0.1](https://github.com/ipfs-shipyard/is-ipfs/compare/v8.0.0...v8.0.1) (2023-01-07) 31 | 32 | 33 | ### Documentation 34 | 35 | * publish api docs ([#68](https://github.com/ipfs-shipyard/is-ipfs/issues/68)) ([310dbf6](https://github.com/ipfs-shipyard/is-ipfs/commit/310dbf6870e64172c2e5bc5e610bb0514e59f3c9)) 36 | 37 | ## [8.0.0](https://github.com/ipfs-shipyard/is-ipfs/compare/v7.0.3...v8.0.0) (2023-01-07) 38 | 39 | 40 | ### ⚠ BREAKING CHANGES 41 | 42 | * bump multiformats from 10.0.3 to 11.0.0 (#67) 43 | 44 | ### Dependencies 45 | 46 | * bump multiformats from 10.0.3 to 11.0.0 ([#67](https://github.com/ipfs-shipyard/is-ipfs/issues/67)) ([680dcd4](https://github.com/ipfs-shipyard/is-ipfs/commit/680dcd4ecbb32a1b5c26814a00baf79c0e9ef496)) 47 | 48 | ## [7.0.3](https://github.com/ipfs-shipyard/is-ipfs/compare/v7.0.2...v7.0.3) (2022-10-18) 49 | 50 | 51 | ### Dependencies 52 | 53 | * bump multiformats from 9.9.0 to 10.0.1 ([#60](https://github.com/ipfs-shipyard/is-ipfs/issues/60)) ([ff0933b](https://github.com/ipfs-shipyard/is-ipfs/commit/ff0933beb4325ef1303813634a2ebdf3a08ec350)) 54 | * bump uint8arrays from 3.1.1 to 4.0.2 ([#59](https://github.com/ipfs-shipyard/is-ipfs/issues/59)) ([5e9af3d](https://github.com/ipfs-shipyard/is-ipfs/commit/5e9af3de48dda65d0213872e591596143ccd4386)) 55 | 56 | ## [7.0.2](https://github.com/ipfs-shipyard/is-ipfs/compare/v7.0.1...v7.0.2) (2022-09-21) 57 | 58 | 59 | ### Documentation 60 | 61 | * update readme toc ([#56](https://github.com/ipfs-shipyard/is-ipfs/issues/56)) ([fb97e30](https://github.com/ipfs-shipyard/is-ipfs/commit/fb97e30a09d76e38512c48ff36857242bb5f8260)) 62 | 63 | ## [7.0.1](https://github.com/ipfs-shipyard/is-ipfs/compare/v7.0.0...v7.0.1) (2022-09-21) 64 | 65 | 66 | ### Documentation 67 | 68 | * update readme examples to be esm ([#55](https://github.com/ipfs-shipyard/is-ipfs/issues/55)) ([e0d1353](https://github.com/ipfs-shipyard/is-ipfs/commit/e0d13534ec18d4290aa4293669c469a56bd7d8b6)) 69 | 70 | ## [7.0.0](https://github.com/ipfs-shipyard/is-ipfs/compare/v6.0.2...v7.0.0) (2022-09-21) 71 | 72 | 73 | ### ⚠ BREAKING CHANGES 74 | 75 | * this module is now ESM only 76 | 77 | ### Features 78 | 79 | * convert to typescript ([#53](https://github.com/ipfs-shipyard/is-ipfs/issues/53)) ([bc9cd3e](https://github.com/ipfs-shipyard/is-ipfs/commit/bc9cd3ecb875aa23a5d3dd1612822fe09ef8b382)) 80 | 81 | ## [6.0.2](https://github.com/ipfs/is-ipfs/compare/v6.0.1...v6.0.2) (2021-08-23) 82 | 83 | 84 | 85 | ## [6.0.1](https://github.com/ipfs/is-ipfs/compare/v6.0.0...v6.0.1) (2021-07-07) 86 | 87 | 88 | 89 | # [6.0.0](https://github.com/ipfs/is-ipfs/compare/v5.0.0...v6.0.0) (2021-07-07) 90 | 91 | 92 | ### chore 93 | 94 | * update to new multiformats ([#42](https://github.com/ipfs/is-ipfs/issues/42)) ([22bf9b7](https://github.com/ipfs/is-ipfs/commit/22bf9b7c88b6a9d6d9141f8565552557d9109ee8)) 95 | 96 | 97 | ### BREAKING CHANGES 98 | 99 | * uses the CID class from the new multiformats module 100 | 101 | Co-authored-by: Marcin Rataj 102 | 103 | 104 | 105 | # [5.0.0](https://github.com/ipfs/is-ipfs/compare/v4.0.0...v5.0.0) (2021-04-19) 106 | 107 | 108 | 109 | # [4.0.0](https://github.com/ipfs/is-ipfs/compare/v3.0.0...v4.0.0) (2021-03-03) 110 | 111 | 112 | ### Bug Fixes 113 | 114 | * **peerMultiaddr:** require /p2p/{key} ([#40](https://github.com/ipfs/is-ipfs/issues/40)) ([25a2436](https://github.com/ipfs/is-ipfs/commit/25a2436d8af7d0ee55c778665b6e7e1d26422216)), closes [#38](https://github.com/ipfs/is-ipfs/issues/38) 115 | 116 | 117 | ### Features 118 | 119 | * add types ([#39](https://github.com/ipfs/is-ipfs/issues/39)) ([4a96bde](https://github.com/ipfs/is-ipfs/commit/4a96bde4a3b83fa625964136f31443b24f83d583)) 120 | 121 | 122 | ### BREAKING CHANGES 123 | 124 | * **peerMultiaddr:** /dnsaddr without explicit /p2p/{key} is no longer a 125 | valid peer multiaddr. See https://github.com/ipfs-shipyard/is-ipfs/issues/38 for rationale why. 126 | 127 | 128 | 129 | # [3.0.0](https://github.com/ipfs/is-ipfs/compare/v2.0.0...v3.0.0) (2021-02-03) 130 | 131 | 132 | ### Features 133 | 134 | * dnsaddr support in peerMultiaddr ([#35](https://github.com/ipfs/is-ipfs/issues/35)) ([4a4710d](https://github.com/ipfs/is-ipfs/commit/4a4710d13b546d8271c6b5f60f214c9010139666)) 135 | * **subdomain:** support inlined DNSLink names ([#36](https://github.com/ipfs/is-ipfs/issues/36)) ([7ab7125](https://github.com/ipfs/is-ipfs/commit/7ab712538fa9a8aee4a3d2cd3496371a8cf0e78b)) 136 | 137 | 138 | 139 | 140 | # [2.0.0](https://github.com/ipfs/is-ipfs/compare/v1.0.3...v2.0.0) (2020-08-10) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * replace node buffers with uint8arrays ([#34](https://github.com/ipfs/is-ipfs/issues/34)) ([ac5ec19](https://github.com/ipfs/is-ipfs/commit/ac5ec19)) 146 | 147 | 148 | ### BREAKING CHANGES 149 | 150 | * this module now only has deps that use Uint8Arrays and not Buffers 151 | 152 | Co-authored-by: Marcin Rataj 153 | 154 | 155 | 156 | 157 | ## [1.0.3](https://github.com/ipfs/is-ipfs/compare/v1.0.2...v1.0.3) (2020-04-22) 158 | 159 | 160 | 161 | 162 | ## [1.0.2](https://github.com/ipfs/is-ipfs/compare/v1.0.1...v1.0.2) (2020-04-22) 163 | 164 | 165 | 166 | 167 | ## [1.0.1](https://github.com/ipfs/is-ipfs/compare/v1.0.0...v1.0.1) (2020-04-22) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * remove bs58 and add buffer ([#33](https://github.com/ipfs/is-ipfs/issues/33)) ([b711186](https://github.com/ipfs/is-ipfs/commit/b711186)) 173 | 174 | 175 | 176 | 177 | # [1.0.0](https://github.com/ipfs/is-ipfs/compare/v0.6.3...v1.0.0) (2020-04-05) 178 | 179 | 180 | ### Features 181 | 182 | * support subdomains in isIPFS.url(url) ([#32](https://github.com/ipfs/is-ipfs/issues/32)) ([22d001d](https://github.com/ipfs/is-ipfs/commit/22d001d)), closes [/github.com/ipfs/is-ipfs/pull/32#discussion_r396161665](https://github.com//github.com/ipfs/is-ipfs/pull/32/issues/discussion_r396161665) 183 | 184 | 185 | ### BREAKING CHANGES 186 | 187 | * `isIPFS.subdomain` now returns true for .ipns.localhost 188 | * `isIPFS.subdomainPattern` changed 189 | 190 | * test: support peer multiaddr with /p2p/ 191 | 192 | Context: https://github.com/libp2p/libp2p/issues/79 193 | 194 | * fix: explicitly ignore URL param and hash 195 | 196 | .url and .path now return true when validating: 197 | https://ipfs.io/ipfs/?filename=name.png#foo 198 | 199 | * refactor: simplify dnslinkSubdomain 200 | 201 | License: MIT 202 | Signed-off-by: Marcin Rataj 203 | 204 | * fix: url() check should include subdomain() 205 | 206 | When .url was created we only had path gateways. When .subdomain was 207 | added, we did not update .url to test for subdomain gateways, which in 208 | the long run will confuse people and feels like a bug. 209 | 210 | Let's fix this: .url() will now check for both subdomain and path gateways 211 | * .url(url) now returns true if .subdomain(url) is true 212 | 213 | * refactor: merge DNSLink check into ipnsSubdomain() 214 | 215 | This makes subdomain checks follow what path gateway checks do, removing 216 | confusion. 217 | 218 | In both cases (IPNS and DNSLink) user needs to perform online record 219 | check, so this is just a handy way of detecting potential matches. 220 | 221 | * docs: update examples 222 | * refactor: switch to iso-url 223 | * refactor: lint-package-json 224 | * chore: update deps 225 | 226 | License: MIT 227 | Signed-off-by: Marcin Rataj 228 | 229 | 230 | 231 | 232 | ## [0.6.3](https://github.com/ipfs/is-ipfs/compare/v0.6.1...v0.6.3) (2020-01-07) 233 | 234 | 235 | 236 | 237 | ## [0.6.2](https://github.com/ipfs/is-ipfs/compare/v0.6.1...v0.6.2) (2020-01-07) 238 | 239 | 240 | 241 | 242 | ## [0.6.1](https://github.com/ipfs/is-ipfs/compare/v0.6.0...v0.6.1) (2019-05-10) 243 | 244 | 245 | 246 | 247 | # [0.6.0](https://github.com/ipfs/is-ipfs/compare/v0.5.1...v0.6.0) (2019-03-03) 248 | 249 | 250 | ### Bug Fixes 251 | 252 | * **ci:** switch to modern .travis.yml ([972ab2e](https://github.com/ipfs/is-ipfs/commit/972ab2e)) 253 | 254 | 255 | ### Features 256 | 257 | * isIPFS.multiaddr(input) ([820d475](https://github.com/ipfs/is-ipfs/commit/820d475)) 258 | * isIPFS.peerMultiaddr(input) ([673dc59](https://github.com/ipfs/is-ipfs/commit/673dc59)) 259 | 260 | 261 | 262 | 263 | ## [0.5.1](https://github.com/ipfs/is-ipfs/compare/v0.5.0...v0.5.1) (2019-02-11) 264 | 265 | 266 | 267 | 268 | # [0.5.0](https://github.com/ipfs/is-ipfs/compare/v0.4.8...v0.5.0) (2019-02-11) 269 | 270 | 271 | ### Bug Fixes 272 | 273 | * double validate ([#25](https://github.com/ipfs/is-ipfs/issues/25)) ([8f8616f](https://github.com/ipfs/is-ipfs/commit/8f8616f)) 274 | 275 | 276 | ### Features 277 | 278 | * add cidPath function ([7be55d3](https://github.com/ipfs/is-ipfs/commit/7be55d3)) 279 | 280 | 281 | 282 | 283 | ## [0.4.8](https://github.com/ipfs/is-ipfs/compare/v0.4.7...v0.4.8) (2018-11-23) 284 | 285 | 286 | 287 | 288 | ## [0.4.7](https://github.com/ipfs/is-ipfs/compare/v0.4.6...v0.4.7) (2018-09-25) 289 | 290 | 291 | ### Bug Fixes 292 | 293 | * switch to cids v0.5.5 ([c07a35f](https://github.com/ipfs/is-ipfs/commit/c07a35f)) 294 | 295 | 296 | 297 | 298 | ## [0.4.6](https://github.com/ipfs/is-ipfs/compare/v0.4.5...v0.4.6) (2018-09-25) 299 | 300 | 301 | 302 | 303 | ## [0.4.5](https://github.com/ipfs/is-ipfs/compare/v0.4.4...v0.4.5) (2018-09-25) 304 | 305 | 306 | 307 | 308 | ## [0.4.4](https://github.com/ipfs/is-ipfs/compare/v0.4.2...v0.4.4) (2018-09-25) 309 | 310 | 311 | 312 | 313 | ## [0.4.3](https://github.com/ipfs/is-ipfs/compare/v0.4.2...v0.4.3) (2018-09-25) 314 | 315 | 316 | 317 | 318 | ## [0.4.2](https://github.com/ipfs/is-ipfs/compare/v0.4.1...v0.4.2) (2018-07-23) 319 | 320 | 321 | 322 | 323 | ## [0.4.1](https://github.com/ipfs/is-ipfs/compare/v0.3.2...v0.4.1) (2018-07-23) 324 | 325 | 326 | ### Bug Fixes 327 | 328 | * release badge in readme ([ae0f738](https://github.com/ipfs/is-ipfs/commit/ae0f738)) 329 | * remove old node builds from TravisCI ([17f9292](https://github.com/ipfs/is-ipfs/commit/17f9292)) 330 | 331 | 332 | ### Features 333 | 334 | * support cidv1b32 in subdomains ([a793da7](https://github.com/ipfs/is-ipfs/commit/a793da7)) 335 | 336 | 337 | 338 | 339 | ## [0.3.2](https://github.com/ipfs/is-ipfs/compare/v0.3.1...v0.3.2) (2017-09-11) 340 | 341 | 342 | 343 | 344 | ## [0.3.1](https://github.com/ipfs/is-ipfs/compare/v0.3.0...v0.3.1) (2017-09-11) 345 | 346 | 347 | ### Features 348 | 349 | * CID Support ([c66bb64](https://github.com/ipfs/is-ipfs/commit/c66bb64)), closes [#12](https://github.com/ipfs/is-ipfs/issues/12) 350 | 351 | 352 | 353 | 354 | # [0.3.0](https://github.com/ipfs/is-ipfs/compare/v0.2.1...v0.3.0) (2017-02-01) 355 | 356 | 357 | ### Bug Fixes 358 | 359 | * **lint:** unnecessary escape ([3c65677](https://github.com/ipfs/is-ipfs/commit/3c65677)) 360 | 361 | 362 | ### Features 363 | 364 | * update scripts for release ([8e85bd7](https://github.com/ipfs/is-ipfs/commit/8e85bd7)) 365 | 366 | 367 | 368 | 369 | ## [0.2.1](https://github.com/ipfs/is-ipfs/compare/v0.1.0...v0.2.1) (2016-10-01) 370 | 371 | 372 | 373 | 374 | # [0.1.0](https://github.com/ipfs/is-ipfs/compare/v0.0.4...v0.1.0) (2016-02-10) 375 | 376 | 377 | 378 | 379 | ## [0.0.4](https://github.com/ipfs/is-ipfs/compare/v0.0.2...v0.0.4) (2016-02-03) 380 | 381 | 382 | 383 | 384 | ## 0.0.2 (2016-02-02) 385 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is dual licensed under MIT and Apache-2.0. 2 | 3 | MIT: https://www.opensource.org/licenses/mit 4 | Apache-2.0: https://www.apache.org/licenses/license-2.0 5 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 2 | 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # is-ipfs 2 | 3 | [![codecov](https://img.shields.io/codecov/c/github/ipfs-shipyard/is-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs-shipyard/is-ipfs) 4 | [![CI](https://img.shields.io/github/actions/workflow/status/ipfs-shipyard/is-ipfs/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs-shipyard/is-ipfs/actions/workflows/js-test-and-release.yml?query=branch%3Amain) 5 | 6 | > A set of utilities to help identify IPFS resources on the web 7 | 8 | # About 9 | 10 | A suite of util methods that provides efficient validation. 11 | 12 | Detection of IPFS Paths and identifiers in URLs is a two-stage process: 13 | 14 | 1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates 15 | 2. proper CID validation is applied to remove false-positives 16 | 17 | ## Example 18 | 19 | ```TypeScript 20 | import * as isIPFS from 'is-ipfs' 21 | 22 | isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 23 | isIPFS.multihash('noop') // false 24 | 25 | isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true (CIDv0) 26 | isIPFS.cid('bafybeiasb5vpmaounyilfuxbd3lryvosl4yefqrfahsb2esg46q6tu6y5q') // true (CIDv1 in Base32) 27 | isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // true (CIDv1 in Base58btc) 28 | isIPFS.cid('noop') // false 29 | 30 | isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // true 31 | isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 32 | 33 | isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 34 | isIPFS.url('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true 35 | isIPFS.url('https://ipfs.io/ipns/github.com') // true 36 | isIPFS.url('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 37 | isIPFS.url('http://en.wikipedia-on-ipfs.org.ipfs.localhost:8080') // true 38 | isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false 39 | isIPFS.url('https://google.com') // false 40 | 41 | isIPFS.path('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 42 | isIPFS.path('/ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb/?weird-filename=test.jpg') // true 43 | isIPFS.path('/ipns/github.com') // true 44 | isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') // false 45 | 46 | isIPFS.urlOrPath('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 47 | isIPFS.urlOrPath('https://ipfs.io/ipns/github.com') // true 48 | isIPFS.urlOrPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 49 | isIPFS.urlOrPath('/ipns/github.com') // true 50 | isIPFS.urlOrPath('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 51 | isIPFS.urlOrPath('https://google.com') // false 52 | 53 | isIPFS.ipfsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 54 | isIPFS.ipfsUrl('https://ipfs.io/ipfs/invalid-hash') // false 55 | 56 | isIPFS.ipnsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 57 | isIPFS.ipnsUrl('https://ipfs.io/ipns/github.com') // true 58 | 59 | isIPFS.ipfsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 60 | isIPFS.ipfsPath('/ipfs/invalid-hash') // false 61 | 62 | isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 63 | isIPFS.ipnsPath('/ipns/github.com') // true 64 | 65 | isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/path/to/file') // true 66 | isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/') // true 67 | isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 68 | isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 69 | isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/file') // false 70 | 71 | isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 72 | isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true 73 | isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false 74 | isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false 75 | 76 | isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 77 | isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false 78 | 79 | isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true 80 | isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') // false 81 | isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false 82 | isIPFS.ipnsSubdomain('http://en.wikipedia-on-ipfs.org.ipns.localhost:8080') // true (assuming DNSLink) 83 | isIPFS.ipnsSubdomain('http://en-wikipedia--on--ipfs-org.ipns.localhost:8080') // true (assuming inlined DNSLink) 84 | isIPFS.ipnsSubdomain('http://hostname-without-tld-.ipns.dweb.link') // false (not a CID, invalid DNS label) 85 | 86 | isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234') // true 87 | isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http') // true 88 | isIPFS.multiaddr('/ip6/::1/udp/1234') // true 89 | isIPFS.multiaddr('ip6/::1/udp/1234') // false 90 | isIPFS.multiaddr('/yoloinvalid/::1/udp/1234') // false 91 | 92 | isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true 93 | isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true (legacy notation) 94 | isIPFS.peerMultiaddr('/ip4/127.0.0.1/tcp/1234/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true 95 | isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true 96 | isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io') // false (key missing, needs additional DNS lookup to tell if this is valid) 97 | isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN') // true (key present, ip and port can be resolved later) 98 | isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false (key missing) 99 | ``` 100 | 101 | # Install 102 | 103 | ```console 104 | $ npm i is-ipfs 105 | ``` 106 | 107 | ## Browser ` 113 | ``` 114 | 115 | # API Docs 116 | 117 | - 118 | 119 | # License 120 | 121 | Licensed under either of 122 | 123 | - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) 124 | - MIT ([LICENSE-MIT](LICENSE-MIT) / ) 125 | 126 | # Contribution 127 | 128 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-ipfs", 3 | "version": "8.0.4", 4 | "description": "A set of utilities to help identify IPFS resources on the web", 5 | "author": "Francisco Dias (http://franciscodias.net/)", 6 | "license": "Apache-2.0 OR MIT", 7 | "homepage": "https://github.com/ipfs-shipyard/is-ipfs#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ipfs-shipyard/is-ipfs.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/ipfs-shipyard/is-ipfs/issues" 14 | }, 15 | "publishConfig": { 16 | "access": "public", 17 | "provenance": true 18 | }, 19 | "keywords": [ 20 | "dnslink", 21 | "gateway", 22 | "ipfs", 23 | "ipns", 24 | "js-ipfs" 25 | ], 26 | "type": "module", 27 | "types": "./dist/src/index.d.ts", 28 | "files": [ 29 | "src", 30 | "dist", 31 | "!dist/test", 32 | "!**/*.tsbuildinfo" 33 | ], 34 | "exports": { 35 | ".": { 36 | "types": "./dist/src/index.d.ts", 37 | "import": "./dist/src/index.js" 38 | } 39 | }, 40 | "eslintConfig": { 41 | "extends": "ipfs", 42 | "parserOptions": { 43 | "project": true, 44 | "sourceType": "module" 45 | } 46 | }, 47 | "release": { 48 | "branches": [ 49 | "main" 50 | ], 51 | "plugins": [ 52 | [ 53 | "@semantic-release/commit-analyzer", 54 | { 55 | "preset": "conventionalcommits", 56 | "releaseRules": [ 57 | { 58 | "breaking": true, 59 | "release": "major" 60 | }, 61 | { 62 | "revert": true, 63 | "release": "patch" 64 | }, 65 | { 66 | "type": "feat", 67 | "release": "minor" 68 | }, 69 | { 70 | "type": "fix", 71 | "release": "patch" 72 | }, 73 | { 74 | "type": "docs", 75 | "release": "patch" 76 | }, 77 | { 78 | "type": "test", 79 | "release": "patch" 80 | }, 81 | { 82 | "type": "deps", 83 | "release": "patch" 84 | }, 85 | { 86 | "scope": "no-release", 87 | "release": false 88 | } 89 | ] 90 | } 91 | ], 92 | [ 93 | "@semantic-release/release-notes-generator", 94 | { 95 | "preset": "conventionalcommits", 96 | "presetConfig": { 97 | "types": [ 98 | { 99 | "type": "feat", 100 | "section": "Features" 101 | }, 102 | { 103 | "type": "fix", 104 | "section": "Bug Fixes" 105 | }, 106 | { 107 | "type": "chore", 108 | "section": "Trivial Changes" 109 | }, 110 | { 111 | "type": "docs", 112 | "section": "Documentation" 113 | }, 114 | { 115 | "type": "deps", 116 | "section": "Dependencies" 117 | }, 118 | { 119 | "type": "test", 120 | "section": "Tests" 121 | } 122 | ] 123 | } 124 | } 125 | ], 126 | "@semantic-release/changelog", 127 | "@semantic-release/npm", 128 | "@semantic-release/github", 129 | "@semantic-release/git" 130 | ] 131 | }, 132 | "scripts": { 133 | "build": "aegir build", 134 | "test": "aegir test", 135 | "test:node": "aegir test -t node --cov", 136 | "test:chrome": "aegir test -t browser --cov", 137 | "test:chrome-webworker": "aegir test -t webworker", 138 | "test:firefox": "aegir test -t browser -- --browser firefox", 139 | "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", 140 | "test:electron-main": "aegir test -t electron-main", 141 | "lint": "aegir lint", 142 | "dep-check": "aegir dep-check", 143 | "release": "aegir release", 144 | "docs": "aegir docs" 145 | }, 146 | "dependencies": { 147 | "@multiformats/mafmt": "^12.1.6", 148 | "@multiformats/multiaddr": "^12.1.14", 149 | "iso-url": "^1.1.3", 150 | "multiformats": "^13.0.1", 151 | "uint8arrays": "^5.0.1" 152 | }, 153 | "devDependencies": { 154 | "aegir": "^42.2.3" 155 | }, 156 | "engines": { 157 | "node": ">=16.0.0", 158 | "npm": ">=7.0.0" 159 | }, 160 | "sideEffects": false 161 | } 162 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * 4 | * A suite of util methods that provides efficient validation. 5 | * 6 | * Detection of IPFS Paths and identifiers in URLs is a two-stage process: 7 | * 8 | * 1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates 9 | * 2. proper CID validation is applied to remove false-positives 10 | * 11 | * @example 12 | * 13 | * ```TypeScript 14 | * import * as isIPFS from 'is-ipfs' 15 | * 16 | * isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 17 | * isIPFS.multihash('noop') // false 18 | * 19 | * isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true (CIDv0) 20 | * isIPFS.cid('bafybeiasb5vpmaounyilfuxbd3lryvosl4yefqrfahsb2esg46q6tu6y5q') // true (CIDv1 in Base32) 21 | * isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // true (CIDv1 in Base58btc) 22 | * isIPFS.cid('noop') // false 23 | * 24 | * isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // true 25 | * isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 26 | * 27 | * isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 28 | * isIPFS.url('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true 29 | * isIPFS.url('https://ipfs.io/ipns/github.com') // true 30 | * isIPFS.url('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 31 | * isIPFS.url('http://en.wikipedia-on-ipfs.org.ipfs.localhost:8080') // true 32 | * isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false 33 | * isIPFS.url('https://google.com') // false 34 | * 35 | * isIPFS.path('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 36 | * isIPFS.path('/ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb/?weird-filename=test.jpg') // true 37 | * isIPFS.path('/ipns/github.com') // true 38 | * isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') // false 39 | * 40 | * isIPFS.urlOrPath('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 41 | * isIPFS.urlOrPath('https://ipfs.io/ipns/github.com') // true 42 | * isIPFS.urlOrPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 43 | * isIPFS.urlOrPath('/ipns/github.com') // true 44 | * isIPFS.urlOrPath('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 45 | * isIPFS.urlOrPath('https://google.com') // false 46 | * 47 | * isIPFS.ipfsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 48 | * isIPFS.ipfsUrl('https://ipfs.io/ipfs/invalid-hash') // false 49 | * 50 | * isIPFS.ipnsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 51 | * isIPFS.ipnsUrl('https://ipfs.io/ipns/github.com') // true 52 | * 53 | * isIPFS.ipfsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true 54 | * isIPFS.ipfsPath('/ipfs/invalid-hash') // false 55 | * 56 | * isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 57 | * isIPFS.ipnsPath('/ipns/github.com') // true 58 | * 59 | * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/path/to/file') // true 60 | * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/') // true 61 | * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 62 | * isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false 63 | * isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/file') // false 64 | * 65 | * isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 66 | * isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true 67 | * isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false 68 | * isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false 69 | * 70 | * isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true 71 | * isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false 72 | * 73 | * isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true 74 | * isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') // false 75 | * isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false 76 | * isIPFS.ipnsSubdomain('http://en.wikipedia-on-ipfs.org.ipns.localhost:8080') // true (assuming DNSLink) 77 | * isIPFS.ipnsSubdomain('http://en-wikipedia--on--ipfs-org.ipns.localhost:8080') // true (assuming inlined DNSLink) 78 | * isIPFS.ipnsSubdomain('http://hostname-without-tld-.ipns.dweb.link') // false (not a CID, invalid DNS label) 79 | * 80 | * isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234') // true 81 | * isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http') // true 82 | * isIPFS.multiaddr('/ip6/::1/udp/1234') // true 83 | * isIPFS.multiaddr('ip6/::1/udp/1234') // false 84 | * isIPFS.multiaddr('/yoloinvalid/::1/udp/1234') // false 85 | * 86 | * isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true 87 | * isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true (legacy notation) 88 | * isIPFS.peerMultiaddr('/ip4/127.0.0.1/tcp/1234/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true 89 | * isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true 90 | * isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io') // false (key missing, needs additional DNS lookup to tell if this is valid) 91 | * isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN') // true (key present, ip and port can be resolved later) 92 | * isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false (key missing) 93 | * ``` 94 | */ 95 | 96 | import * as mafmt from '@multiformats/mafmt' 97 | import { multiaddr } from '@multiformats/multiaddr' 98 | import { URL } from 'iso-url' 99 | import { base32 } from 'multiformats/bases/base32' 100 | import { base58btc } from 'multiformats/bases/base58' 101 | import { CID } from 'multiformats/cid' 102 | import * as Digest from 'multiformats/hashes/digest' 103 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string' 104 | import type { Multiaddr } from '@multiformats/multiaddr' 105 | 106 | export const pathGatewayPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/ 107 | export const pathPattern = /^\/(ip[fn]s)\/([^/?#]+)/ 108 | const defaultProtocolMatch = 1 109 | const defaultHashMath = 2 110 | 111 | // CID, libp2p-key or DNSLink 112 | export const subdomainGatewayPattern = /^https?:\/\/([^/]+)\.(ip[fn]s)\.[^/?]+/ 113 | const subdomainIdMatch = 1 114 | const subdomainProtocolMatch = 2 115 | 116 | // Fully qualified domain name (FQDN) that has an explicit .tld suffix 117 | const fqdnWithTld = /^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$/ 118 | 119 | function isMultihash (hash: Uint8Array | string): boolean { 120 | const formatted = convertToString(hash) 121 | 122 | if (formatted === false) { 123 | return false 124 | } 125 | 126 | try { 127 | Digest.decode(base58btc.decode(`z${formatted}`)) 128 | } catch { 129 | return false 130 | } 131 | 132 | return true 133 | } 134 | 135 | function isMultiaddr (input: string | Uint8Array | Multiaddr): input is Multiaddr { 136 | try { 137 | return Boolean(multiaddr(input)) 138 | } catch { 139 | return false 140 | } 141 | } 142 | 143 | function isBase32EncodedMultibase (hash: CID | string | Uint8Array): boolean { 144 | try { 145 | let cid: CID | null 146 | 147 | if (isString(hash)) { 148 | cid = CID.parse(hash) 149 | } else { 150 | cid = CID.asCID(hash) 151 | } 152 | 153 | if (cid == null) { 154 | return false 155 | } 156 | 157 | base32.decode(cid.toString()) 158 | } catch { 159 | return false 160 | } 161 | 162 | return true 163 | } 164 | 165 | function isCID (hash: CID | Uint8Array | string): hash is CID { 166 | try { 167 | if (isString(hash)) { 168 | return Boolean(CID.parse(hash)) 169 | } 170 | 171 | if (hash instanceof Uint8Array) { 172 | return Boolean(CID.decode(hash)) 173 | } 174 | 175 | return Boolean(CID.asCID(hash)) // eslint-disable-line no-new 176 | } catch { 177 | return false 178 | } 179 | } 180 | 181 | /** 182 | * @param {string | Uint8Array | Multiaddr} input 183 | */ 184 | function isPeerMultiaddr (input: string | Uint8Array | Multiaddr): boolean { 185 | return isMultiaddr(input) && mafmt.P2P.matches(input) 186 | } 187 | 188 | /** 189 | * @param {string | Uint8Array} input 190 | * @param {RegExp | string} pattern 191 | * @param {number} [protocolMatch=1] 192 | * @param {number} [hashMatch=2] 193 | */ 194 | function isIpfs (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath): boolean { 195 | const formatted = convertToString(input) 196 | if (formatted === false) { 197 | return false 198 | } 199 | 200 | const match = formatted.match(pattern) 201 | if (match == null) { 202 | return false 203 | } 204 | 205 | if (match[protocolMatch] !== 'ipfs') { 206 | return false 207 | } 208 | 209 | let hash = match[hashMatch] 210 | 211 | if (hash != null && pattern === subdomainGatewayPattern) { 212 | // when doing checks for subdomain context 213 | // ensure hash is case-insensitive 214 | // (browsers force-lowercase authority component anyway) 215 | hash = hash.toLowerCase() 216 | } 217 | 218 | return isCID(hash) 219 | } 220 | 221 | /** 222 | * 223 | * @param {string | Uint8Array} input 224 | * @param {string | RegExp} pattern 225 | * @param {number} [protocolMatch=1] 226 | * @param {number} [hashMatch=1] 227 | */ 228 | function isIpns (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath): boolean { 229 | const formatted = convertToString(input) 230 | if (formatted === false) { 231 | return false 232 | } 233 | const match = formatted.match(pattern) 234 | if (match == null) { 235 | return false 236 | } 237 | 238 | if (match[protocolMatch] !== 'ipns') { 239 | return false 240 | } 241 | 242 | let ipnsId = match[hashMatch] 243 | 244 | if (ipnsId != null && pattern === subdomainGatewayPattern) { 245 | // when doing checks for subdomain context 246 | // ensure ipnsId is case-insensitive 247 | // (browsers force-lowercase authority compotent anyway) 248 | ipnsId = ipnsId.toLowerCase() 249 | // Check if it is cidv1 250 | if (isCID(ipnsId)) return true 251 | // Check if it looks like FQDN 252 | try { 253 | if (!ipnsId.includes('.') && ipnsId.includes('-')) { 254 | // name without tld, assuming its inlined into a single DNS label 255 | // (https://github.com/ipfs/in-web-browsers/issues/169) 256 | // en-wikipedia--on--ipfs-org → en.wikipedia-on-ipfs.org 257 | ipnsId = ipnsId.replace(/--/g, '@').replace(/-/g, '.').replace(/@/g, '-') 258 | } 259 | // URL implementation in web browsers forces lowercase of the hostname 260 | const { hostname } = new URL(`http://${ipnsId}`) // eslint-disable-line no-new 261 | // Check if potential FQDN has an explicit TLD 262 | return fqdnWithTld.test(hostname) 263 | } catch (e) { 264 | return false 265 | } 266 | } 267 | 268 | return true 269 | } 270 | 271 | /** 272 | * @param {any} input 273 | */ 274 | function isString (input: any): input is string { 275 | return typeof input === 'string' 276 | } 277 | 278 | /** 279 | * @param {Uint8Array | string} input 280 | */ 281 | function convertToString (input: Uint8Array | string): string | false { 282 | if (input instanceof Uint8Array) { 283 | return uint8ArrayToString(input, 'base58btc') 284 | } 285 | 286 | if (isString(input)) { 287 | return input 288 | } 289 | 290 | return false 291 | } 292 | 293 | /** 294 | * Returns `true` if the provided `url` string includes a valid IPFS subdomain 295 | * (case-insensitive CIDv1) or `false` otherwise. 296 | */ 297 | export const ipfsSubdomain = (url: string | Uint8Array): boolean => isIpfs(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch) 298 | 299 | /** 300 | * Returns `true` if the provided `url` string looks like a valid IPNS subdomain 301 | * (CIDv1 with `libp2p-key` multicodec or something that looks like a FQDN, for 302 | * example `en.wikipedia-on-ipfs.org.ipns.localhost:8080`) or `false` otherwise. 303 | * 304 | * **Note:** `ipnsSubdomain` method works in offline mode: it does not perform 305 | * actual IPNS record lookup over DHT or other content routing method. It may 306 | * return false-positives: 307 | * 308 | * - To ensure IPNS record exists, make a call to `/api/v0/name/resolve?arg=` 309 | * - To ensure DNSLink exists, make a call to `/api/v0/dns?arg=` 310 | */ 311 | export const ipnsSubdomain = (url: string | Uint8Array): boolean => isIpns(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch) 312 | 313 | /** 314 | * Returns `true` if the provided `url` string includes a valid IPFS, looks like 315 | * an IPNS/DNSLink subdomain or `false` otherwise. 316 | */ 317 | export const subdomain = (url: string | Uint8Array): boolean => ipfsSubdomain(url) || ipnsSubdomain(url) 318 | 319 | /** 320 | * Returns `true` if the provided string is a valid IPFS url or `false` 321 | * otherwise. 322 | */ 323 | export const ipfsUrl = (url: string | Uint8Array): boolean => isIpfs(url, pathGatewayPattern) || ipfsSubdomain(url) 324 | 325 | /** 326 | * Returns `true` if the provided string is a valid IPNS url or `false` 327 | * otherwise. 328 | */ 329 | export const ipnsUrl = (url: string | Uint8Array): boolean => isIpns(url, pathGatewayPattern) || ipnsSubdomain(url) 330 | 331 | /** 332 | * Returns `true` if the provided string is a valid IPFS or IPNS url or `false` 333 | * otherwise. 334 | */ 335 | export const url = (url: string | Uint8Array): boolean => ipfsUrl(url) || ipnsUrl(url) || subdomain(url) 336 | export const path = (path: string | Uint8Array): boolean => isIpfs(path, pathPattern) || isIpns(path, pathPattern) 337 | 338 | /** 339 | * Returns `true` if the provided string or `Uint8Array` is a valid `multihash` 340 | * or `false` otherwise. 341 | */ 342 | export { isMultihash as multihash } 343 | 344 | /** 345 | * Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr) 346 | * or `Uint8Array` represents a valid multiaddr or `false` otherwise. 347 | */ 348 | export { isMultiaddr as multiaddr } 349 | 350 | /** 351 | * Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr) 352 | * or `Uint8Array` represents a valid libp2p peer multiaddr (matching [`P2P` 353 | * format from `mafmt`](https://github.com/multiformats/js-mafmt#api)) or 354 | * `false` otherwise. 355 | */ 356 | export { isPeerMultiaddr as peerMultiaddr } 357 | 358 | /** 359 | * Returns `true` if the provided string, `Uint8Array` or [`CID`](https://github.com/multiformats/js-multiformats/#readme) 360 | * object represents a valid [CID](https://docs.ipfs.io/guides/concepts/cid/) or 361 | * `false` otherwise. 362 | */ 363 | export { isCID as cid } 364 | 365 | /** 366 | * Returns `true` if the provided string is a valid `CID` in Base32 encoding or 367 | * `false` otherwise. 368 | */ 369 | export const base32cid = (cid: CID | string | Uint8Array): boolean => (isCID(cid) && isBase32EncodedMultibase(cid)) 370 | 371 | /** 372 | * Returns `true` if the provided string is a valid IPFS or IPNS path or `false` 373 | * otherwise. 374 | */ 375 | export const ipfsPath = (path: string | Uint8Array): boolean => isIpfs(path, pathPattern) 376 | 377 | /** 378 | * Returns `true` if the provided string is a valid IPNS path or `false` 379 | * otherwise. 380 | */ 381 | export const ipnsPath = (path: string | Uint8Array): boolean => isIpns(path, pathPattern) 382 | 383 | /** 384 | * Returns `true` if the provided string is a valid IPFS or IPNS url or path or 385 | * `false` otherwise. 386 | */ 387 | export const urlOrPath = (x: string | Uint8Array): boolean => url(x) || path(x) 388 | 389 | /** 390 | * Returns `true` if the provided string is a valid "CID path" (IPFS path 391 | * without `/ipfs/` prefix) or `false` otherwise. 392 | */ 393 | export const cidPath = (path: string | Uint8Array | CID): boolean => isString(path) && !isCID(path) && isIpfs(`/ipfs/${path}`, pathPattern) 394 | -------------------------------------------------------------------------------- /test/test-cid.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import { CID } from 'multiformats/cid' 5 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 6 | import * as isIPFS from '../src/index.js' 7 | 8 | describe('ipfs cid', () => { 9 | it('isIPFS.cid should match a valid CID instance', (done) => { 10 | const cid = CID.parse('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') 11 | const actual = isIPFS.cid(cid) 12 | expect(actual).to.equal(true) 13 | done() 14 | }) 15 | 16 | it('isIPFS.cid should match a valid CIDv0 (multihash)', (done) => { 17 | const actual = isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') 18 | expect(actual).to.equal(true) 19 | done() 20 | }) 21 | 22 | it('isIPFS.cid should match a valid CIDv0 (multihash) Uint8Array', (done) => { 23 | const actual = isIPFS.cid(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 24 | expect(actual).to.equal(true) 25 | done() 26 | }) 27 | 28 | it('isIPFS.cid should not match a broken CIDv0 Uint8Array', (done) => { 29 | const actual = isIPFS.cid(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE70')) 30 | expect(actual).to.equal(false) 31 | done() 32 | }) 33 | 34 | it('isIPFS.cid should not match an invalid CIDv0 (multihash with a typo)', (done) => { 35 | const actual = isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE70') 36 | expect(actual).to.equal(false) 37 | done() 38 | }) 39 | 40 | it('isIPFS.cid should match a valid CIDv1 in Base58btc', (done) => { 41 | const actual = isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') 42 | expect(actual).to.equal(true) 43 | done() 44 | }) 45 | 46 | it('isIPFS.cid should match a valid CIDv1 in Base32', (done) => { 47 | const actual = isIPFS.cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') 48 | expect(actual).to.equal(true) 49 | done() 50 | }) 51 | 52 | it('isIPFS.cid should not match an invalid CIDv1 (with a typo)', (done) => { 53 | const actual = isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ') 54 | expect(actual).to.equal(false) 55 | done() 56 | }) 57 | 58 | it('isIPFS.cid should not match an invalid CID', (done) => { 59 | const actual = isIPFS.cid('noop') 60 | expect(actual).to.equal(false) 61 | done() 62 | }) 63 | 64 | it('isIPFS.cid should not match an invalid CID data type', (done) => { 65 | // @ts-expect-error invalid input 66 | const actual = isIPFS.cid(4) 67 | expect(actual).to.equal(false) 68 | done() 69 | }) 70 | }) 71 | 72 | describe('ipfs base32cid', () => { 73 | it('isIPFS.base32cid should not match a valid CIDv0 (multihash in base58btc)', (done) => { 74 | const actual = isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') 75 | expect(actual).to.equal(false) 76 | done() 77 | }) 78 | 79 | it('isIPFS.base32cid should not match a valid CIDv1 in base58btc', (done) => { 80 | const actual = isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') 81 | expect(actual).to.equal(true) 82 | done() 83 | }) 84 | 85 | it('isIPFS.base32cid should match a valid URL-safe CIDv1 in Base32', (done) => { 86 | const actual = isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') 87 | expect(actual).to.equal(true) 88 | done() 89 | }) 90 | 91 | it('isIPFS.base32cid should not match an invalid CID (with a typo)', (done) => { 92 | const actual = isIPFS.base32cid('afybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') 93 | expect(actual).to.equal(false) 94 | done() 95 | }) 96 | 97 | it('isIPFS.base32cid should not match an invalid CID', (done) => { 98 | const actual = isIPFS.base32cid('noop') 99 | expect(actual).to.equal(false) 100 | done() 101 | }) 102 | 103 | it('isIPFS.base32cid should not match an invalid CID data type', (done) => { 104 | // @ts-expect-error data type is invalid 105 | const actual = isIPFS.base32cid(4) 106 | expect(actual).to.equal(false) 107 | done() 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /test/test-multiaddr.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { multiaddr } from '@multiformats/multiaddr' 4 | import { expect } from 'aegir/chai' 5 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 6 | import * as isIPFS from '../src/index.js' 7 | 8 | describe('ipfs multiaddr', () => { 9 | it('isIPFS.multiaddr should match a string with valid ip4 multiaddr', (done) => { 10 | const actual = isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http') 11 | expect(actual).to.equal(true) 12 | done() 13 | }) 14 | 15 | it('isIPFS.multiaddr should match a string with valid ip6 multiaddr', (done) => { 16 | const actual = isIPFS.multiaddr('/ip6/::1/udp/1234/http') 17 | expect(actual).to.equal(true) 18 | done() 19 | }) 20 | 21 | it('isIPFS.multiaddr should match a string with valid dnsaddr multiaddr (no key)', (done) => { 22 | const actual = isIPFS.multiaddr('/dnsaddr/bootstrap.libp2p.io') 23 | expect(actual).to.equal(true) 24 | done() 25 | }) 26 | 27 | it('isIPFS.multiaddr should match a string with valid dnsaddr multiaddr (with key)', (done) => { 28 | const actual = isIPFS.multiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN') 29 | expect(actual).to.equal(true) 30 | done() 31 | }) 32 | 33 | it('isIPFS.multiaddr should match a valid Multiaddr instance', (done) => { 34 | const ma = multiaddr('/ip6/::1/udp/1234/http') 35 | const actual = isIPFS.multiaddr(ma) 36 | expect(actual).to.equal(true) 37 | done() 38 | }) 39 | 40 | it('isIPFS.multiaddr should match a Uint8Array with multiaddr', (done) => { 41 | const ma = multiaddr('/ip6/::1/udp/1234/http') 42 | const actual = isIPFS.multiaddr(ma.bytes) 43 | expect(actual).to.equal(true) 44 | done() 45 | }) 46 | 47 | it('isIPFS.multiaddr should not match random Uint8Array', (done) => { 48 | const actual = isIPFS.multiaddr(uint8ArrayFromString('randomUint8Array')) 49 | expect(actual).to.equal(false) 50 | done() 51 | }) 52 | 53 | it('isIPFS.multiaddr should not match an invalid multiaddr (no initial slash)', (done) => { 54 | const actual = isIPFS.multiaddr('ip4/127.0.0.1/udp/1234/http') 55 | expect(actual).to.equal(false) 56 | done() 57 | }) 58 | 59 | it('isIPFS.multiaddr should not match an invalid multiaddr (unknown namespace)', (done) => { 60 | const actual = isIPFS.multiaddr('/yoloinvalid/127.0.0.1/udp/1234/http') 61 | expect(actual).to.equal(false) 62 | done() 63 | }) 64 | 65 | it('isIPFS.multiaddr should not match an invalid multiaddr', (done) => { 66 | const actual = isIPFS.multiaddr('noop') 67 | expect(actual).to.equal(false) 68 | done() 69 | }) 70 | 71 | it('isIPFS.multiaddr should not match an invalid multiaddr data type', (done) => { 72 | // @ts-expect-error invalid input 73 | const actual = isIPFS.multiaddr(4) 74 | expect(actual).to.equal(false) 75 | done() 76 | }) 77 | }) 78 | 79 | describe('ipfs peerMultiaddr', () => { 80 | // https://github.com/multiformats/js-mafmt/blob/v6.0.6/test/index.spec.js#L137 81 | const goodCircuit = [ 82 | '/p2p-circuit', 83 | '/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', // /ipfs/ is legacy notation replaced with /p2p/ 84 | '/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', 85 | '/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', 86 | '/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', 87 | '/p2p-circuit/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 88 | '/p2p-circuit/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 89 | '/p2p-circuit/ip4/1.2.3.4/tcp/3456/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 90 | '/p2p-circuit/ip4/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 91 | '/p2p-circuit/ip4/127.0.0.1/tcp/4002/ipfs/QmddWMcQX6orJGHpETYMyPgXrCXCtYANMFVDCvhKoDwLqA', 92 | '/p2p-circuit/ipfs/QmddWMcQX6orJGHpETYMyPgXrCXCtYANMFVDCvhKoDwLqA', 93 | '/p2p-circuit/p2p/QmddWMcQX6orJGHpETYMyPgXrCXCtYANMFVDCvhKoDwLqA', 94 | '/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj/' + 95 | 'p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj' 96 | ] 97 | // https://github.com/multiformats/js-mafmt/blob/v6.0.6/test/index.spec.js#L157 98 | const validPeerMultiaddrs = [ 99 | '/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', 100 | '/ip4/127.0.0.1/tcp/20008/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', 101 | '/ip4/127.0.0.1/tcp/20008/ws/p2p/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5', // ed25519+identity multihash 102 | '/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 103 | '/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 104 | '/ip4/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4', 105 | '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 106 | '/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit', 107 | '/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj' 108 | ].concat(goodCircuit) 109 | 110 | it('isIPFS.peerMultiaddr should match a string with a valid IPFS peer', (done) => { 111 | for (const addr of validPeerMultiaddrs) { 112 | const actual = isIPFS.peerMultiaddr(addr) 113 | expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true) 114 | } 115 | done() 116 | }) 117 | 118 | it('isIPFS.peerMultiaddr should match a valid Multiaddr instance', (done) => { 119 | for (const addr of validPeerMultiaddrs) { 120 | const ma = multiaddr(addr) 121 | const actual = isIPFS.peerMultiaddr(ma) 122 | expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true) 123 | } 124 | done() 125 | }) 126 | 127 | it('isIPFS.peerMultiaddr should match a Uint8Array with multiaddr', (done) => { 128 | for (const addr of validPeerMultiaddrs) { 129 | const ma = multiaddr(addr) 130 | const actual = isIPFS.peerMultiaddr(ma.bytes) 131 | expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true) 132 | } 133 | done() 134 | }) 135 | 136 | it('isIPFS.peerMultiaddr should not match random Uint8Array', (done) => { 137 | const actual = isIPFS.peerMultiaddr(uint8ArrayFromString('randomUint8Array')) 138 | expect(actual).to.equal(false) 139 | done() 140 | }) 141 | 142 | it('isIPFS.peerMultiaddr should not match an invalid multiaddr (no initial slash)', (done) => { 143 | const actual = isIPFS.peerMultiaddr('ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') 144 | expect(actual).to.equal(false) 145 | done() 146 | }) 147 | 148 | it('isIPFS.peerMultiaddr should not match /dnsaddr multiaddr without explicit /p2p/{key}', (done) => { 149 | // https://github.com/ipfs-shipyard/is-ipfs/issues/38 150 | const actual = isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io') 151 | expect(actual).to.equal(false) 152 | done() 153 | }) 154 | 155 | it('isIPFS.peerMultiaddr should not match an invalid multiaddr (unknown namespace)', (done) => { 156 | const actual = isIPFS.peerMultiaddr('/yoloinvalid/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') 157 | expect(actual).to.equal(false) 158 | done() 159 | }) 160 | 161 | it('isIPFS.peerMultiaddr should not match an invalid multiaddr', (done) => { 162 | const actual = isIPFS.peerMultiaddr('noop') 163 | expect(actual).to.equal(false) 164 | done() 165 | }) 166 | 167 | it('isIPFS.peerMultiaddr should not match an invalid multiaddr data type', (done) => { 168 | // @ts-expect-error data type is invalid 169 | const actual = isIPFS.peerMultiaddr(4) 170 | expect(actual).to.equal(false) 171 | done() 172 | }) 173 | }) 174 | -------------------------------------------------------------------------------- /test/test-multihash.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 5 | import * as isIPFS from '../src/index.js' 6 | 7 | describe('ipfs multihash', () => { 8 | it('isIPFS.multihash should match a valid multihash', (done) => { 9 | const actual = isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') 10 | expect(actual).to.equal(true) 11 | done() 12 | }) 13 | 14 | it('isIPFS.multihash should match a valid multihash Uint8Array', (done) => { 15 | const actual = isIPFS.multihash(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 16 | expect(actual).to.equal(true) 17 | done() 18 | }) 19 | 20 | it('isIPFS.multihash should not match a Uint8Array', (done) => { 21 | const actual = isIPFS.multihash(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE70')) 22 | expect(actual).to.equal(false) 23 | done() 24 | }) 25 | 26 | it('isIPFS.multihash should not match an invalid multihash (with a typo)', (done) => { 27 | const actual = isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE70') 28 | expect(actual).to.equal(false) 29 | done() 30 | }) 31 | 32 | it('isIPFS.multihash should not match an invalid multihash', (done) => { 33 | const actual = isIPFS.multihash('noop') 34 | expect(actual).to.equal(false) 35 | done() 36 | }) 37 | 38 | it('isIPFS.multihash should not match an invalid multihash data type', (done) => { 39 | // @ts-expect-error invalid input 40 | const actual = isIPFS.multihash(4) 41 | expect(actual).to.equal(false) 42 | done() 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/test-path.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 5 | import * as isIPFS from '../src/index.js' 6 | 7 | describe('ipfs path', () => { 8 | it('isIPFS.ipfsPath should match an ipfs path', (done) => { 9 | const actual = isIPFS.ipfsPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm?arg=val#hash') 10 | expect(actual).to.equal(true) 11 | done() 12 | }) 13 | 14 | it('isIPFS.ipfsPath should match a complex ipfs path', (done) => { 15 | const actual = isIPFS.ipfsPath('/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html?arg=val#hash') 16 | expect(actual).to.equal(true) 17 | done() 18 | }) 19 | 20 | it('isIPFS.ipfsPath should not match an ipns path', (done) => { 21 | const actual = isIPFS.ipfsPath('/ipns/github.com/') 22 | expect(actual).to.equal(false) 23 | done() 24 | }) 25 | 26 | it('isIPFS.ipfsPath should not match a github ipfs repo href', (done) => { 27 | const actual = isIPFS.ipfsPath('/ipfs/js-ipfs/blob/master/README.md') 28 | expect(actual).to.equal(false) 29 | done() 30 | }) 31 | 32 | it('isIPFS.ipfsPath should not match a path without prefix', (done) => { 33 | const actual = isIPFS.ipfsPath('/foo.bar') 34 | expect(actual).to.equal(false) 35 | done() 36 | }) 37 | 38 | it('isIPFS.ipfsPath should not match a Uint8Array data type', (done) => { 39 | const actual = isIPFS.ipfsPath(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 40 | expect(actual).to.equal(false) 41 | done() 42 | }) 43 | 44 | it('isIPFS.ipnsPath should not match an ipfs path', (done) => { 45 | const actual = isIPFS.ipnsPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 46 | expect(actual).to.equal(false) 47 | done() 48 | }) 49 | 50 | it('isIPFS.ipnsPath should match an ipns path', (done) => { 51 | const actual = isIPFS.ipnsPath('/ipns/github.com/') 52 | expect(actual).to.equal(true) 53 | done() 54 | }) 55 | 56 | it('isIPFS.ipnsPath should not match a github ipfs repo path', (done) => { 57 | const actual = isIPFS.ipnsPath('/ipfs/js-ipfs/blob/master/README.md') 58 | expect(actual).to.equal(false) 59 | done() 60 | }) 61 | 62 | it('isIPFS.ipnsPath should not match a path without prefix', (done) => { 63 | const actual = isIPFS.ipnsPath('/foo.bar') 64 | expect(actual).to.equal(false) 65 | done() 66 | }) 67 | 68 | it('isIPFS.ipnsPath should not match a Uint8Array data type', (done) => { 69 | const actual = isIPFS.ipnsPath(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 70 | expect(actual).to.equal(false) 71 | done() 72 | }) 73 | 74 | it('isIPFS.path should match an ipfs path', (done) => { 75 | const actual = isIPFS.path('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 76 | expect(actual).to.equal(true) 77 | done() 78 | }) 79 | 80 | it('isIPFS.path should match an ipns path', (done) => { 81 | const actual = isIPFS.path('/ipns/github.com/') 82 | expect(actual).to.equal(true) 83 | done() 84 | }) 85 | 86 | it('isIPFS.path should not match a github ipfs repo path', (done) => { 87 | const actual = isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') 88 | expect(actual).to.equal(false) 89 | done() 90 | }) 91 | 92 | it('isIPFS.path should not match an path without prefix', (done) => { 93 | const actual = isIPFS.path('/foo.bar') 94 | expect(actual).to.equal(false) 95 | done() 96 | }) 97 | 98 | it('isIPFS.path should not match a Uint8Array data type', (done) => { 99 | const actual = isIPFS.path(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 100 | expect(actual).to.equal(false) 101 | done() 102 | }) 103 | 104 | it('isIPFS.urlOrPath should match ipfs url', (done) => { 105 | const actual = isIPFS.urlOrPath('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 106 | expect(actual).to.equal(true) 107 | done() 108 | }) 109 | 110 | it('isIPFS.urlOrPath should match ipns url', (done) => { 111 | const actual = isIPFS.urlOrPath('http://ipfs.io/ipns/foo.bar.com') 112 | expect(actual).to.equal(true) 113 | done() 114 | }) 115 | 116 | it('isIPFS.urlOrPath should match a path', (done) => { 117 | const actual = isIPFS.urlOrPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 118 | expect(actual).to.equal(true) 119 | done() 120 | }) 121 | 122 | it('isIPFS.urlOrPath should match ipns path', (done) => { 123 | const actual = isIPFS.urlOrPath('/ipns/foo.bar.com') 124 | expect(actual).to.equal(true) 125 | done() 126 | }) 127 | 128 | it('isIPFS.urlOrPath should not match a Uint8Array data type', (done) => { 129 | const actual = isIPFS.ipfsPath(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 130 | expect(actual).to.equal(false) 131 | done() 132 | }) 133 | 134 | it('isIPFS.cidPath should match a CID path', () => { 135 | const actual = isIPFS.cidPath('QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm/path/to/file') 136 | expect(actual).to.equal(true) 137 | }) 138 | 139 | it('isIPFS.cidPath should match a CID path with trailing slash', () => { 140 | const actual = isIPFS.cidPath('QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm/') 141 | expect(actual).to.equal(true) 142 | }) 143 | 144 | it('isIPFS.cidPath should not match a CID', () => { 145 | const actual = isIPFS.cidPath('QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 146 | expect(actual).to.equal(false) 147 | }) 148 | 149 | it('isIPFS.cidPath should not match a non string', () => { 150 | // @ts-expect-error data type is invalid 151 | const actual = isIPFS.cidPath({ toString: () => 'QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm/path/to/file' }) 152 | expect(actual).to.equal(false) 153 | }) 154 | 155 | it('isIPFS.cidPath should not match an IPFS path', () => { 156 | const actual = isIPFS.cidPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 157 | expect(actual).to.equal(false) 158 | }) 159 | }) 160 | -------------------------------------------------------------------------------- /test/test-subdomain.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 5 | import * as isIPFS from '../src/index.js' 6 | 7 | describe('ipfs subdomain', () => { 8 | it('isIPFS.ipfsSubdomain should match a cidv1b32', (done) => { 9 | const actual = isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 10 | expect(actual).to.equal(true) 11 | done() 12 | }) 13 | 14 | it('isIPFS.ipfsSubdomain should match a cidv1b32 with complex ipfs path', (done) => { 15 | const actual = isIPFS.ipfsSubdomain('http://bafybeidvtwx54qr44kidymvhfzefzxhgkieigwth6oswk75zhlzjdmunoy.ipfs.dweb.link/linkify-demo.html') 16 | expect(actual).to.equal(true) 17 | done() 18 | }) 19 | 20 | it('isIPFS.ipfsSubdomain should match localhost with port', (done) => { 21 | const actual = isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.localhost:8080') 22 | expect(actual).to.equal(true) 23 | done() 24 | }) 25 | 26 | it('isIPFS.ipfsSubdomain should not match non-cid subdomains', (done) => { 27 | const actual = isIPFS.ipfsSubdomain('http://not-a-cid.ipfs.dweb.link') 28 | expect(actual).to.equal(false) 29 | done() 30 | }) 31 | 32 | it('isIPFS.ipfsSubdomain should not match case-sensitive CID subdomains', (done) => { 33 | // Origin forces browsers to lowercase the authority component 34 | // so QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR 35 | // becomes invalid CID: qmbwqxbekc3p8tqskc98xmwnzrzdtrlmimpl8wbutgsmnr 36 | const actual = isIPFS.ipfsSubdomain('http://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR.ipfs.dweb.link') 37 | expect(actual).to.equal(false) 38 | done() 39 | }) 40 | 41 | it('isIPFS.ipfsSubdomain should not match if not under .ipfs. zone', (done) => { 42 | // we require explicit convention of putting cidv1b32 under .ipfs. zone 43 | // to make it clear content can be safely uplifted and loaded over IPFS 44 | const actual = isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') 45 | expect(actual).to.equal(false) 46 | done() 47 | }) 48 | 49 | it('isIPFS.ipfsSubdomain should not match a Uint8Array data type', (done) => { 50 | const actual = isIPFS.ipfsSubdomain(uint8ArrayFromString('QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR', 'base58btc')) 51 | expect(actual).to.equal(false) 52 | done() 53 | }) 54 | 55 | it('isIPFS.ipnsSubdomain should not match .ipfs. zone', (done) => { 56 | const actual = isIPFS.ipnsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 57 | expect(actual).to.equal(false) 58 | done() 59 | }) 60 | 61 | it('isIPFS.ipnsSubdomain should match a .ipns. zone with cidv1b32', (done) => { 62 | const actual = isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') 63 | expect(actual).to.equal(true) 64 | done() 65 | }) 66 | 67 | it('isIPFS.ipnsSubdomain should not match case-sensitive CID subdomains', (done) => { 68 | // Origin forces browsers to lowercase the authority component 69 | // so QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR 70 | // becomes invalid CID: qmbwqxbekc3p8tqskc98xmwnzrzdtrlmimpl8wbutgsmnr 71 | const actual = isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') 72 | expect(actual).to.equal(false) 73 | done() 74 | }) 75 | 76 | it('isIPFS.ipnsSubdomain should not match without .ipns. zone', (done) => { 77 | const actual = isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') 78 | expect(actual).to.equal(false) 79 | done() 80 | }) 81 | 82 | it('isIPFS.ipnsSubdomain should not match a Uint8Array data type', (done) => { 83 | const actual = isIPFS.ipnsSubdomain(uint8ArrayFromString('QmNQuBJ8tg4QN6mSLXHekxBbcToPwKxamWNrDdEugxMTDd', 'base58btc')) 84 | expect(actual).to.equal(false) 85 | done() 86 | }) 87 | 88 | it('isIPFS.ipnsSubdomain should match .ipns.localhost zone with FQDN', (done) => { 89 | const actual = isIPFS.ipnsSubdomain('http://docs.ipfs.io.ipns.localhost:8080/some/path') 90 | expect(actual).to.equal(true) 91 | done() 92 | }) 93 | 94 | it('isIPFS.ipnsSubdomain should match .ipns.sub.sub.domain.tld zone with FQDN', (done) => { 95 | const actual = isIPFS.ipnsSubdomain('http://docs.ipfs.io.ipns.foo.bar.buzz.dweb.link') 96 | expect(actual).to.equal(true) 97 | done() 98 | }) 99 | 100 | it('isIPFS.ipnsSubdomain should match *.ipns. zone with FQDN', (done) => { 101 | const actual = isIPFS.ipnsSubdomain('http://docs.ipfs.io.ipns.locahost:8080') 102 | expect(actual).to.equal(true) 103 | done() 104 | }) 105 | 106 | it('isIPFS.ipnsSubdomain should match .ipns. zone with cidv1b32', (done) => { 107 | const actual = isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') 108 | expect(actual).to.equal(true) 109 | done() 110 | }) 111 | 112 | it('isIPFS.ipnsSubdomain should not match if *.ipns is not a valid hostname', (done) => { 113 | const actual = isIPFS.ipnsSubdomain('http://invalid-hostname-.ipns.dweb.link') 114 | expect(actual).to.equal(false) 115 | done() 116 | }) 117 | 118 | it('isIPFS.ipnsSubdomain should match if *.ipns is FQDN inlined into a single DNS label', (done) => { 119 | const actual = isIPFS.ipnsSubdomain('https://en-wikipedia--on--ipfs-org.ipns.dweb.link') 120 | expect(actual).to.equal(true) 121 | done() 122 | }) 123 | 124 | it('isIPFS.subdomain should match an ipfs subdomain', (done) => { 125 | const actual = isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 126 | expect(actual).to.equal(true) 127 | done() 128 | }) 129 | 130 | it('isIPFS.subdomain should match an ipns subdomain with PeerID as cidv1b32', (done) => { 131 | const actual = isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') 132 | expect(actual).to.equal(true) 133 | done() 134 | }) 135 | 136 | it('isIPFS.subdomain should match .ipns.localhost zone with FQDN', (done) => { 137 | // we do not support opaque strings in subdomains, only peerids 138 | const actual = isIPFS.subdomain('http://docs.ipfs.io.ipns.dweb.link') 139 | expect(actual).to.equal(true) 140 | done() 141 | }) 142 | 143 | it('isIPFS.subdomain should not match if fqdn does not start with cidv1b32', (done) => { 144 | const actual = isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 145 | expect(actual).to.equal(false) 146 | done() 147 | }) 148 | 149 | it('isIPFS.subdomain should not match if no ipfs/ipns zone', (done) => { 150 | const actual = isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') 151 | expect(actual).to.equal(false) 152 | done() 153 | }) 154 | 155 | it('isIPFS.subdomain should not match if *.ipns is not libp2pkey nor fqdn', (done) => { 156 | const actual = isIPFS.subdomain('http://not-a-cid-or-valid-hostname-.ipns.dweb.link') 157 | expect(actual).to.equal(false) 158 | done() 159 | }) 160 | 161 | it('isIPFS.subdomain should match if *.ipns looks like an inlined DNSLink name', (done) => { 162 | const actual = isIPFS.subdomain('http://en-wikipedia--on--ipfs-org.ipns.dweb.link') 163 | expect(actual).to.equal(true) 164 | done() 165 | }) 166 | 167 | it('isIPFS.subdomain should not match a Uint8Array data type', (done) => { 168 | const actual = isIPFS.subdomain(uint8ArrayFromString('QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR', 'base58btc')) 169 | expect(actual).to.equal(false) 170 | done() 171 | }) 172 | 173 | it('isIPFS.urlOrPath should match ipfs url with cidv1b32 subdomain', (done) => { 174 | const actual = isIPFS.urlOrPath('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 175 | expect(actual).to.equal(true) 176 | done() 177 | }) 178 | 179 | it('isIPFS.urlOrPath should match subdomain ipns', (done) => { 180 | const actual = isIPFS.urlOrPath('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') 181 | expect(actual).to.equal(true) 182 | done() 183 | }) 184 | 185 | it('isIPFS.urlOrPath should match potential DNSLink in subdomain', (done) => { 186 | const actual = isIPFS.urlOrPath('http://a-dnslink-website.com.ipns.localhost:8080') 187 | expect(actual).to.equal(true) 188 | done() 189 | }) 190 | 191 | it('isIPFS.url should match ipfs url with cidv1b32 subdomain', (done) => { 192 | const actual = isIPFS.url('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') 193 | expect(actual).to.equal(true) 194 | done() 195 | }) 196 | 197 | it('isIPFS.url should match subdomain ipns', (done) => { 198 | const actual = isIPFS.url('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') 199 | expect(actual).to.equal(true) 200 | done() 201 | }) 202 | 203 | it('isIPFS.url should match potential DNSLink in subdomain', (done) => { 204 | const actual = isIPFS.url('http://a-dnslink-website.com.ipns.localhost:8080') 205 | expect(actual).to.equal(true) 206 | done() 207 | }) 208 | }) 209 | -------------------------------------------------------------------------------- /test/test-url.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 5 | import * as isIPFS from '../src/index.js' 6 | 7 | describe('ipfs url', () => { 8 | it('isIPFS.ipfsUrl should match an ipfs url', (done) => { 9 | const actual = isIPFS.ipfsUrl('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm?arg=val#hash') 10 | expect(actual).to.equal(true) 11 | done() 12 | }) 13 | 14 | it('isIPFS.ipfsUrl should match a complex ipfs url', (done) => { 15 | const actual = isIPFS.ipfsUrl('http://ipfs.alexandria.media/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html?arg=val#hash') 16 | expect(actual).to.equal(true) 17 | done() 18 | }) 19 | 20 | it('isIPFS.ipfsUrl should not match an ipns url', (done) => { 21 | const actual = isIPFS.ipfsUrl('http://ipfs.io/ipns/github.com/') 22 | expect(actual).to.equal(false) 23 | done() 24 | }) 25 | 26 | it('isIPFS.ipfsUrl should not match a github ipfs repo url', (done) => { 27 | const actual = isIPFS.ipfsUrl('https://github.com/ipfs/js-ipfs/blob/master/README.md') 28 | expect(actual).to.equal(false) 29 | done() 30 | }) 31 | 32 | it('isIPFS.ipfsUrl should not match an google url', (done) => { 33 | const actual = isIPFS.ipfsUrl('https://google.com') 34 | expect(actual).to.equal(false) 35 | done() 36 | }) 37 | 38 | it('isIPFS.ipfsUrl should not match a Uint8Array input', (done) => { 39 | const actual = isIPFS.ipfsUrl(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 40 | expect(actual).to.equal(false) 41 | done() 42 | }) 43 | 44 | it('isIPFS.ipnsUrl should not match an ipfs url', (done) => { 45 | const actual = isIPFS.ipnsUrl('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 46 | expect(actual).to.equal(false) 47 | done() 48 | }) 49 | 50 | it('isIPFS.ipnsUrl should match an ipns url', (done) => { 51 | const actual = isIPFS.ipnsUrl('http://ipfs.io/ipns/github.com/') 52 | expect(actual).to.equal(true) 53 | done() 54 | }) 55 | 56 | it('isIPFS.ipnsUrl should not match a github ipfs repo url', (done) => { 57 | const actual = isIPFS.ipnsUrl('https://github.com/ipfs/js-ipfs/blob/master/README.md') 58 | expect(actual).to.equal(false) 59 | done() 60 | }) 61 | 62 | it('isIPFS.ipnsUrl should not match an google url', (done) => { 63 | const actual = isIPFS.ipnsUrl('https://google.com') 64 | expect(actual).to.equal(false) 65 | done() 66 | }) 67 | 68 | it('isIPFS.ipnsUrl should not match a Uint8Array input', (done) => { 69 | const actual = isIPFS.ipnsUrl(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 70 | expect(actual).to.equal(false) 71 | done() 72 | }) 73 | 74 | it('isIPFS.url should match an ipfs url', (done) => { 75 | const actual = isIPFS.url('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') 76 | expect(actual).to.equal(true) 77 | done() 78 | }) 79 | 80 | it('isIPFS.url should match an ipns url', (done) => { 81 | const actual = isIPFS.url('http://ipfs.io/ipns/github.com/') 82 | expect(actual).to.equal(true) 83 | done() 84 | }) 85 | 86 | it('isIPFS.url should not match a github ipfs repo url', (done) => { 87 | const actual = isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') 88 | expect(actual).to.equal(false) 89 | done() 90 | }) 91 | 92 | it('isIPFS.url should not match an google url', (done) => { 93 | const actual = isIPFS.url('https://google.com') 94 | expect(actual).to.equal(false) 95 | done() 96 | }) 97 | 98 | it('isIPFS.url should not match a Uint8Array input', (done) => { 99 | const actual = isIPFS.url(uint8ArrayFromString('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o', 'base58btc')) 100 | expect(actual).to.equal(false) 101 | done() 102 | }) 103 | }) 104 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": [ 7 | "src", 8 | "test" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "./src/index.ts" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------