├── .DS_Store ├── .github ├── dependabot.yml └── workflows │ ├── automerge.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 ├── record.proto ├── record.ts ├── selectors.ts ├── utils.ts └── validators.ts ├── test ├── fixtures │ ├── go-key-records.ts │ └── go-record.ts ├── record.spec.ts ├── selection.spec.ts ├── utils.spec.ts └── validator.spec.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libp2p/js-libp2p-record/47cff8bb50d3e26f63a5771123ed220f2bb54b1a/.DS_Store -------------------------------------------------------------------------------- /.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: 10 9 | commit-message: 10 | prefix: "deps" 11 | prefix-development: "deps(dev)" 12 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | # File managed by web3-bot. DO NOT EDIT. 2 | # See https://github.com/protocol/.github/ for details. 3 | 4 | name: Automerge 5 | on: [ pull_request ] 6 | 7 | jobs: 8 | automerge: 9 | uses: protocol/.github/.github/workflows/automerge.yml@master 10 | with: 11 | job: 'automerge' 12 | -------------------------------------------------------------------------------- /.github/workflows/js-test-and-release.yml: -------------------------------------------------------------------------------- 1 | # File managed by web3-bot. DO NOT EDIT. 2 | # See https://github.com/protocol/.github/ for details. 3 | 4 | name: test & maybe release 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | 13 | check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: lts/* 20 | - uses: ipfs/aegir/actions/cache-node-modules@master 21 | - run: npm run --if-present lint 22 | - run: npm run --if-present dep-check 23 | 24 | test-node: 25 | needs: check 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [windows-latest, ubuntu-latest, macos-latest] 30 | node: [lts/*] 31 | fail-fast: true 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions/setup-node@v3 35 | with: 36 | node-version: ${{ matrix.node }} 37 | - uses: ipfs/aegir/actions/cache-node-modules@master 38 | - run: npm run --if-present test:node 39 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 40 | with: 41 | flags: node 42 | 43 | test-chrome: 44 | needs: check 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | - uses: actions/setup-node@v3 49 | with: 50 | node-version: lts/* 51 | - uses: ipfs/aegir/actions/cache-node-modules@master 52 | - run: npm run --if-present test:chrome 53 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 54 | with: 55 | flags: chrome 56 | 57 | test-chrome-webworker: 58 | needs: check 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v3 62 | - uses: actions/setup-node@v3 63 | with: 64 | node-version: lts/* 65 | - uses: ipfs/aegir/actions/cache-node-modules@master 66 | - run: npm run --if-present test:chrome-webworker 67 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 68 | with: 69 | flags: chrome-webworker 70 | 71 | test-firefox: 72 | needs: check 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v3 76 | - uses: actions/setup-node@v3 77 | with: 78 | node-version: lts/* 79 | - uses: ipfs/aegir/actions/cache-node-modules@master 80 | - run: npm run --if-present test:firefox 81 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 82 | with: 83 | flags: firefox 84 | 85 | test-firefox-webworker: 86 | needs: check 87 | runs-on: ubuntu-latest 88 | steps: 89 | - uses: actions/checkout@v3 90 | - uses: actions/setup-node@v3 91 | with: 92 | node-version: lts/* 93 | - uses: ipfs/aegir/actions/cache-node-modules@master 94 | - run: npm run --if-present test:firefox-webworker 95 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 96 | with: 97 | flags: firefox-webworker 98 | 99 | test-webkit: 100 | needs: check 101 | runs-on: ${{ matrix.os }} 102 | strategy: 103 | matrix: 104 | os: [ubuntu-latest, macos-latest] 105 | node: [lts/*] 106 | fail-fast: true 107 | steps: 108 | - uses: actions/checkout@v3 109 | - uses: actions/setup-node@v3 110 | with: 111 | node-version: lts/* 112 | - uses: ipfs/aegir/actions/cache-node-modules@master 113 | - run: npm run --if-present test:webkit 114 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 115 | with: 116 | flags: webkit 117 | 118 | test-webkit-webworker: 119 | needs: check 120 | runs-on: ${{ matrix.os }} 121 | strategy: 122 | matrix: 123 | os: [ubuntu-latest, macos-latest] 124 | node: [lts/*] 125 | fail-fast: true 126 | steps: 127 | - uses: actions/checkout@v3 128 | - uses: actions/setup-node@v3 129 | with: 130 | node-version: lts/* 131 | - uses: ipfs/aegir/actions/cache-node-modules@master 132 | - run: npm run --if-present test:webkit-webworker 133 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 134 | with: 135 | flags: webkit-webworker 136 | 137 | test-electron-main: 138 | needs: check 139 | runs-on: ubuntu-latest 140 | steps: 141 | - uses: actions/checkout@v3 142 | - uses: actions/setup-node@v3 143 | with: 144 | node-version: lts/* 145 | - uses: ipfs/aegir/actions/cache-node-modules@master 146 | - run: npx xvfb-maybe npm run --if-present test:electron-main 147 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 148 | with: 149 | flags: electron-main 150 | 151 | test-electron-renderer: 152 | needs: check 153 | runs-on: ubuntu-latest 154 | steps: 155 | - uses: actions/checkout@v3 156 | - uses: actions/setup-node@v3 157 | with: 158 | node-version: lts/* 159 | - uses: ipfs/aegir/actions/cache-node-modules@master 160 | - run: npx xvfb-maybe npm run --if-present test:electron-renderer 161 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1 162 | with: 163 | flags: electron-renderer 164 | 165 | release: 166 | needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-webkit, test-webkit-webworker, test-electron-main, test-electron-renderer] 167 | runs-on: ubuntu-latest 168 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 169 | steps: 170 | - uses: actions/checkout@v3 171 | with: 172 | fetch-depth: 0 173 | - uses: actions/setup-node@v3 174 | with: 175 | node-version: lts/* 176 | - uses: ipfs/aegir/actions/cache-node-modules@master 177 | - uses: ipfs/aegir/actions/docker-login@master 178 | with: 179 | docker-token: ${{ secrets.DOCKER_TOKEN }} 180 | docker-username: ${{ secrets.DOCKER_USERNAME }} 181 | - run: npm run --if-present release 182 | env: 183 | GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN || github.token }} 184 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 185 | -------------------------------------------------------------------------------- /.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 and mark stale issue 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | permissions: 8 | issues: write 9 | pull-requests: write 10 | 11 | jobs: 12 | stale: 13 | uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .docs 4 | .coverage 5 | package-lock.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.4](https://github.com/libp2p/js-libp2p-record/compare/v3.0.3...v3.0.4) (2023-06-15) 2 | 3 | 4 | ### Trivial Changes 5 | 6 | * Update .github/workflows/semantic-pull-request.yml [skip ci] ([afec2c9](https://github.com/libp2p/js-libp2p-record/commit/afec2c9d1685707c9cff82342099839abb6976da)) 7 | * Update .github/workflows/stale.yml [skip ci] ([2d12a78](https://github.com/libp2p/js-libp2p-record/commit/2d12a789581d65181d463fa9b908280b14fc2070)) 8 | 9 | 10 | ### Dependencies 11 | 12 | * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#95](https://github.com/libp2p/js-libp2p-record/issues/95)) ([30e0fb5](https://github.com/libp2p/js-libp2p-record/commit/30e0fb5dfb193e3289f1aec6c29a8d194fd9aa92)) 13 | 14 | ## [3.0.3](https://github.com/libp2p/js-libp2p-record/compare/v3.0.2...v3.0.3) (2023-04-04) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * correction package.json exports types path ([#87](https://github.com/libp2p/js-libp2p-record/issues/87)) ([c1e9a6d](https://github.com/libp2p/js-libp2p-record/commit/c1e9a6d402a971b3ef66484c151b9dc4627fea1b)) 20 | 21 | ## [3.0.2](https://github.com/libp2p/js-libp2p-record/compare/v3.0.1...v3.0.2) (2023-03-10) 22 | 23 | 24 | ### Dependencies 25 | 26 | * bump protons-runtime from 4.0.2 to 5.0.0 ([#73](https://github.com/libp2p/js-libp2p-record/issues/73)) ([4b1b67b](https://github.com/libp2p/js-libp2p-record/commit/4b1b67bac77cb13a01ce330a4a93eb6c3dc042a5)) 27 | 28 | ## [3.0.1](https://github.com/libp2p/js-libp2p-record/compare/v3.0.0...v3.0.1) (2023-03-10) 29 | 30 | 31 | ### Trivial Changes 32 | 33 | * replace err-code with CodeError ([#71](https://github.com/libp2p/js-libp2p-record/issues/71)) ([a843ae4](https://github.com/libp2p/js-libp2p-record/commit/a843ae4fbdc8c262a55f4ed87f989770d3783c5a)), closes [js-libp2p#1269](https://github.com/libp2p/js-libp2p/issues/1269) 34 | * Update .github/workflows/semantic-pull-request.yml [skip ci] ([3982918](https://github.com/libp2p/js-libp2p-record/commit/3982918a51c25bf1f803702073eaf17cc5feee9b)) 35 | * Update .github/workflows/semantic-pull-request.yml [skip ci] ([79984c0](https://github.com/libp2p/js-libp2p-record/commit/79984c0ce651cb0bff634b3b8df630cf807baa59)) 36 | * Update .github/workflows/semantic-pull-request.yml [skip ci] ([7ccccc7](https://github.com/libp2p/js-libp2p-record/commit/7ccccc73fb1aad74cd4b696acd8dc912199afec3)) 37 | 38 | 39 | ### Dependencies 40 | 41 | * **dev:** bump aegir from 37.12.1 to 38.1.7 ([#84](https://github.com/libp2p/js-libp2p-record/issues/84)) ([4cc5935](https://github.com/libp2p/js-libp2p-record/commit/4cc593576ccda281950f30aa6c8e769baa1aeee6)) 42 | 43 | ## [3.0.0](https://github.com/libp2p/js-libp2p-record/compare/v2.0.4...v3.0.0) (2023-01-06) 44 | 45 | 46 | ### ⚠ BREAKING CHANGES 47 | 48 | * update multiformats to 11.x.x (#70) 49 | 50 | ### Bug Fixes 51 | 52 | * update multiformats to 11.x.x ([#70](https://github.com/libp2p/js-libp2p-record/issues/70)) ([594fc41](https://github.com/libp2p/js-libp2p-record/commit/594fc4171ec20f4fc1fbc36c99c61eed06aeab25)) 53 | 54 | ## [2.0.4](https://github.com/libp2p/js-libp2p-record/compare/v2.0.3...v2.0.4) (2022-12-16) 55 | 56 | 57 | ### Documentation 58 | 59 | * publish api docs ([#68](https://github.com/libp2p/js-libp2p-record/issues/68)) ([5a3dd41](https://github.com/libp2p/js-libp2p-record/commit/5a3dd419f13b67e27c19f3b23252937d80fc8b93)) 60 | 61 | ## [2.0.3](https://github.com/libp2p/js-libp2p-record/compare/v2.0.2...v2.0.3) (2022-10-12) 62 | 63 | 64 | ### Trivial Changes 65 | 66 | * Update .github/workflows/stale.yml [skip ci] ([92044b6](https://github.com/libp2p/js-libp2p-record/commit/92044b646180e2b2d0f495d26cc54c184ad6fb7b)) 67 | 68 | 69 | ### Dependencies 70 | 71 | * bump uint8arrays, protons and multiformats ([#63](https://github.com/libp2p/js-libp2p-record/issues/63)) ([9106a6a](https://github.com/libp2p/js-libp2p-record/commit/9106a6abdc71a2c94359759bbc2f61213e9a6a0b)) 72 | 73 | ## [2.0.2](https://github.com/libp2p/js-libp2p-record/compare/v2.0.1...v2.0.2) (2022-08-11) 74 | 75 | 76 | ### Dependencies 77 | 78 | * update protons to 5.1.0 ([#58](https://github.com/libp2p/js-libp2p-record/issues/58)) ([24d4047](https://github.com/libp2p/js-libp2p-record/commit/24d404733aa89c28ae71abfcb51ee20b6af919cf)) 79 | 80 | ## [2.0.1](https://github.com/libp2p/js-libp2p-record/compare/v2.0.0...v2.0.1) (2022-08-03) 81 | 82 | 83 | ### Trivial Changes 84 | 85 | * update project ([#53](https://github.com/libp2p/js-libp2p-record/issues/53)) ([1927144](https://github.com/libp2p/js-libp2p-record/commit/1927144ce346592f513e2f29e0b4677dd1feb468)) 86 | 87 | 88 | ### Dependencies 89 | 90 | * update deps to support no-copy operations ([#55](https://github.com/libp2p/js-libp2p-record/issues/55)) ([7be8515](https://github.com/libp2p/js-libp2p-record/commit/7be8515ad87d062bbc9db20fc3134ed06b1286a9)) 91 | 92 | ## [2.0.0](https://github.com/libp2p/js-libp2p-record/compare/v1.0.5...v2.0.0) (2022-06-15) 93 | 94 | 95 | ### ⚠ BREAKING CHANGES 96 | 97 | * uses new single-issue libp2p interface modules 98 | 99 | ### Features 100 | 101 | * update to latest libp2p interfaces ([#45](https://github.com/libp2p/js-libp2p-record/issues/45)) ([b5eb989](https://github.com/libp2p/js-libp2p-record/commit/b5eb9897f23ebf39e2a728672f3727222bc1159f)) 102 | 103 | ### [1.0.5](https://github.com/libp2p/js-libp2p-record/compare/v1.0.4...v1.0.5) (2022-05-25) 104 | 105 | 106 | ### Trivial Changes 107 | 108 | * **deps:** bump @libp2p/interfaces from 1.3.32 to 2.0.2 ([#43](https://github.com/libp2p/js-libp2p-record/issues/43)) ([992677b](https://github.com/libp2p/js-libp2p-record/commit/992677bd6bf432b3ce894c53ac7a721e2dd44bf9)) 109 | 110 | ### [1.0.4](https://github.com/libp2p/js-libp2p-record/compare/v1.0.3...v1.0.4) (2022-04-14) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * pad ns correctly ([#41](https://github.com/libp2p/js-libp2p-record/issues/41)) ([18030d9](https://github.com/libp2p/js-libp2p-record/commit/18030d9d3832a7d09dee928923909875a5780a2f)) 116 | 117 | ### [1.0.3](https://github.com/libp2p/js-libp2p-record/compare/v1.0.2...v1.0.3) (2022-04-13) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * update interfaces ([#40](https://github.com/libp2p/js-libp2p-record/issues/40)) ([e2713a3](https://github.com/libp2p/js-libp2p-record/commit/e2713a3a6b5351e2dc012cf734ff1c945479920b)) 123 | 124 | ### [1.0.2](https://github.com/libp2p/js-libp2p-record/compare/v1.0.1...v1.0.2) (2022-04-09) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * use protons ([#39](https://github.com/libp2p/js-libp2p-record/issues/39)) ([10b4cc2](https://github.com/libp2p/js-libp2p-record/commit/10b4cc2600e8f3bed9a2d646b68b0b2107e1caa4)) 130 | 131 | ### [1.0.1](https://github.com/libp2p/js-libp2p-record/compare/v1.0.0...v1.0.1) (2022-03-24) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * export selector/validators with the same name as their prefix ([#34](https://github.com/libp2p/js-libp2p-record/issues/34)) ([4913d1f](https://github.com/libp2p/js-libp2p-record/commit/4913d1fec2ed92d4803f3497bef81142bd560a91)) 137 | 138 | ## [1.0.0](https://github.com/libp2p/js-libp2p-record/compare/v0.10.6...v1.0.0) (2022-02-18) 139 | 140 | 141 | ### ⚠ BREAKING CHANGES 142 | 143 | * switch to named exports, ESM only 144 | 145 | ### Features 146 | 147 | * convert to typescript ([#32](https://github.com/libp2p/js-libp2p-record/issues/32)) ([89cc2ef](https://github.com/libp2p/js-libp2p-record/commit/89cc2ef5234835c82ea29ff54a4887d630921ae3)) 148 | 149 | ## [0.10.6](https://github.com/libp2p/js-libp2p-record/compare/v0.10.5...v0.10.6) (2021-09-24) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * auto select if only one record ([#31](https://github.com/libp2p/js-libp2p-record/issues/31)) ([53bc7f2](https://github.com/libp2p/js-libp2p-record/commit/53bc7f2627a95256337033977a05df54a534f951)) 155 | 156 | 157 | 158 | ## [0.10.5](https://github.com/libp2p/js-libp2p-record/compare/v0.10.4...v0.10.5) (2021-08-18) 159 | 160 | 161 | 162 | ## [0.10.4](https://github.com/libp2p/js-libp2p-record/compare/v0.10.3...v0.10.4) (2021-07-07) 163 | 164 | 165 | 166 | ## [0.10.3](https://github.com/libp2p/js-libp2p-record/compare/v0.10.2...v0.10.3) (2021-04-22) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * use dht selectors and validators from interfaces ([#28](https://github.com/libp2p/js-libp2p-record/issues/28)) ([7b211a5](https://github.com/libp2p/js-libp2p-record/commit/7b211a528675018abbc8e4674bedbdd5ab7b5eea)) 172 | 173 | 174 | 175 | ## [0.10.2](https://github.com/libp2p/js-libp2p-record/compare/v0.10.1...v0.10.2) (2021-04-20) 176 | 177 | 178 | ### Bug Fixes 179 | 180 | * specify pbjs root ([#27](https://github.com/libp2p/js-libp2p-record/issues/27)) ([32ddb1d](https://github.com/libp2p/js-libp2p-record/commit/32ddb1deec71543d0ef34157b6ef2d271e8408f5)) 181 | 182 | 183 | 184 | ## [0.10.1](https://github.com/libp2p/js-libp2p-record/compare/v0.10.0...v0.10.1) (2021-04-07) 185 | 186 | 187 | 188 | # [0.10.0](https://github.com/libp2p/js-libp2p-record/compare/v0.8.0...v0.10.0) (2021-02-02) 189 | 190 | 191 | ### Features 192 | 193 | * add types and update deps ([#25](https://github.com/libp2p/js-libp2p-record/issues/25)) ([e2395de](https://github.com/libp2p/js-libp2p-record/commit/e2395de924a9c71d761c6ea3f5aab2844b252591)) 194 | 195 | 196 | 197 | 198 | # [0.9.0](https://github.com/libp2p/js-libp2p-record/compare/v0.8.0...v0.9.0) (2020-08-07) 199 | 200 | 201 | 202 | 203 | # [0.8.0](https://github.com/libp2p/js-libp2p-record/compare/v0.7.3...v0.8.0) (2020-07-29) 204 | 205 | 206 | ### Bug Fixes 207 | 208 | * support uint8arrays in place of node buffers ([#23](https://github.com/libp2p/js-libp2p-record/issues/23)) ([3b99ee1](https://github.com/libp2p/js-libp2p-record/commit/3b99ee1)) 209 | 210 | 211 | ### BREAKING CHANGES 212 | 213 | * takes Uint8Arrays as well as Node Buffers 214 | 215 | 216 | 217 | 218 | ## [0.7.3](https://github.com/libp2p/js-libp2p-record/compare/v0.7.2...v0.7.3) (2020-04-27) 219 | 220 | 221 | ### Bug Fixes 222 | 223 | * remove buffer ([#21](https://github.com/libp2p/js-libp2p-record/issues/21)) ([80fb248](https://github.com/libp2p/js-libp2p-record/commit/80fb248)) 224 | 225 | 226 | 227 | 228 | ## [0.7.2](https://github.com/libp2p/js-libp2p-record/compare/v0.7.1...v0.7.2) (2020-02-13) 229 | 230 | 231 | ### Bug Fixes 232 | 233 | * remove use of assert module ([#18](https://github.com/libp2p/js-libp2p-record/issues/18)) ([57e24a7](https://github.com/libp2p/js-libp2p-record/commit/57e24a7)) 234 | 235 | 236 | 237 | 238 | ## [0.7.1](https://github.com/libp2p/js-libp2p-record/compare/v0.7.0...v0.7.1) (2020-01-03) 239 | 240 | 241 | 242 | 243 | # [0.7.0](https://github.com/libp2p/js-libp2p-record/compare/v0.6.3...v0.7.0) (2019-08-16) 244 | 245 | 246 | ### Code Refactoring 247 | 248 | * convert from callbacks to async ([#13](https://github.com/libp2p/js-libp2p-record/issues/13)) ([42eab95](https://github.com/libp2p/js-libp2p-record/commit/42eab95)) 249 | 250 | 251 | ### BREAKING CHANGES 252 | 253 | * All places in the API that used callbacks are now replaced with async/await 254 | 255 | 256 | 257 | 258 | ## [0.6.3](https://github.com/libp2p/js-libp2p-record/compare/v0.6.2...v0.6.3) (2019-05-23) 259 | 260 | 261 | ### Bug Fixes 262 | 263 | * remove leftpad ([#16](https://github.com/libp2p/js-libp2p-record/issues/16)) ([4f46885](https://github.com/libp2p/js-libp2p-record/commit/4f46885)) 264 | 265 | 266 | 267 | 268 | ## [0.6.2](https://github.com/libp2p/js-libp2p-record/compare/v0.6.1...v0.6.2) (2019-02-20) 269 | 270 | 271 | 272 | 273 | ## [0.6.1](https://github.com/libp2p/js-libp2p-record/compare/v0.6.0...v0.6.1) (2018-11-08) 274 | 275 | 276 | 277 | 278 | # [0.6.0](https://github.com/libp2p/js-libp2p-record/compare/v0.5.1...v0.6.0) (2018-10-18) 279 | 280 | 281 | ### Features 282 | 283 | * new record definition ([#8](https://github.com/libp2p/js-libp2p-record/issues/8)) ([10177ae](https://github.com/libp2p/js-libp2p-record/commit/10177ae)) 284 | 285 | 286 | ### BREAKING CHANGES 287 | 288 | * having the libp2p-record protobuf definition compliant with go-libp2p-record. Author and signature were removed. 289 | 290 | 291 | 292 | 293 | ## [0.5.1](https://github.com/libp2p/js-libp2p-record/compare/v0.5.0...v0.5.1) (2017-09-07) 294 | 295 | 296 | ### Features 297 | 298 | * replace protocol-buffers with protons ([#5](https://github.com/libp2p/js-libp2p-record/issues/5)) ([8774a4f](https://github.com/libp2p/js-libp2p-record/commit/8774a4f)) 299 | 300 | 301 | 302 | 303 | # [0.5.0](https://github.com/libp2p/js-libp2p-record/compare/v0.4.0...v0.5.0) (2017-09-03) 304 | 305 | 306 | ### Features 307 | 308 | * p2p addrs situation ([#4](https://github.com/libp2p/js-libp2p-record/issues/4)) ([bcba43c](https://github.com/libp2p/js-libp2p-record/commit/bcba43c)) 309 | 310 | 311 | 312 | 313 | # [0.4.0](https://github.com/libp2p/js-libp2p-record/compare/v0.3.1...v0.4.0) (2017-07-22) 314 | 315 | 316 | 317 | 318 | ## [0.3.1](https://github.com/libp2p/js-libp2p-record/compare/v0.3.0...v0.3.1) (2017-03-29) 319 | 320 | 321 | 322 | 323 | # 0.3.0 (2017-03-29) 324 | -------------------------------------------------------------------------------- /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 | # 📁 Archived - this module has been merged into [js-libp2p](https://github.com/libp2p/js-libp2p/tree/master/packages/kad-dht) 2 | 3 | # @libp2p/record 4 | 5 | [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) 6 | [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) 7 | [![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-record.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-record) 8 | [![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-record/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p-record/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) 9 | 10 | > libp2p record implementation 11 | 12 | ## Table of contents 13 | 14 | - [Install](#install) 15 | - [Browser ` 33 | ``` 34 | 35 | ## Description 36 | 37 | Implementation of [go-libp2p-record](https://github.com/libp2p/go-libp2p-record) in JavaScript. 38 | 39 | ## API Docs 40 | 41 | - 42 | 43 | ## License 44 | 45 | Licensed under either of 46 | 47 | - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) 48 | - MIT ([LICENSE-MIT](LICENSE-MIT) / ) 49 | 50 | ## Contribution 51 | 52 | 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. 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@libp2p/record", 3 | "version": "3.0.4", 4 | "description": "libp2p record implementation", 5 | "author": "Friedel Ziegelmayer ", 6 | "license": "Apache-2.0 OR MIT", 7 | "homepage": "https://github.com/libp2p/js-libp2p-record#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/libp2p/js-libp2p-record.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/libp2p/js-libp2p-record/issues" 14 | }, 15 | "keywords": [ 16 | "IPFS" 17 | ], 18 | "engines": { 19 | "node": ">=16.0.0", 20 | "npm": ">=7.0.0" 21 | }, 22 | "type": "module", 23 | "types": "./dist/src/index.d.ts", 24 | "typesVersions": { 25 | "*": { 26 | "*": [ 27 | "*", 28 | "dist/*", 29 | "dist/src/*", 30 | "dist/src/*/index" 31 | ], 32 | "src/*": [ 33 | "*", 34 | "dist/*", 35 | "dist/src/*", 36 | "dist/src/*/index" 37 | ] 38 | } 39 | }, 40 | "files": [ 41 | "src", 42 | "dist", 43 | "!dist/test", 44 | "!**/*.tsbuildinfo" 45 | ], 46 | "exports": { 47 | ".": { 48 | "types": "./dist/src/index.d.ts", 49 | "import": "./dist/src/index.js" 50 | }, 51 | "./selectors": { 52 | "types": "./dist/src/selectors.d.ts", 53 | "import": "./dist/src/selectors.js" 54 | }, 55 | "./validators": { 56 | "types": "./dist/src/validators.d.ts", 57 | "import": "./dist/src/validators.js" 58 | } 59 | }, 60 | "eslintConfig": { 61 | "extends": "ipfs", 62 | "parserOptions": { 63 | "sourceType": "module" 64 | }, 65 | "ignorePatterns": [ 66 | "src/record.d.ts" 67 | ] 68 | }, 69 | "release": { 70 | "branches": [ 71 | "master" 72 | ], 73 | "plugins": [ 74 | [ 75 | "@semantic-release/commit-analyzer", 76 | { 77 | "preset": "conventionalcommits", 78 | "releaseRules": [ 79 | { 80 | "breaking": true, 81 | "release": "major" 82 | }, 83 | { 84 | "revert": true, 85 | "release": "patch" 86 | }, 87 | { 88 | "type": "feat", 89 | "release": "minor" 90 | }, 91 | { 92 | "type": "fix", 93 | "release": "patch" 94 | }, 95 | { 96 | "type": "docs", 97 | "release": "patch" 98 | }, 99 | { 100 | "type": "test", 101 | "release": "patch" 102 | }, 103 | { 104 | "type": "deps", 105 | "release": "patch" 106 | }, 107 | { 108 | "scope": "no-release", 109 | "release": false 110 | } 111 | ] 112 | } 113 | ], 114 | [ 115 | "@semantic-release/release-notes-generator", 116 | { 117 | "preset": "conventionalcommits", 118 | "presetConfig": { 119 | "types": [ 120 | { 121 | "type": "feat", 122 | "section": "Features" 123 | }, 124 | { 125 | "type": "fix", 126 | "section": "Bug Fixes" 127 | }, 128 | { 129 | "type": "chore", 130 | "section": "Trivial Changes" 131 | }, 132 | { 133 | "type": "docs", 134 | "section": "Documentation" 135 | }, 136 | { 137 | "type": "deps", 138 | "section": "Dependencies" 139 | }, 140 | { 141 | "type": "test", 142 | "section": "Tests" 143 | } 144 | ] 145 | } 146 | } 147 | ], 148 | "@semantic-release/changelog", 149 | "@semantic-release/npm", 150 | "@semantic-release/github", 151 | "@semantic-release/git" 152 | ] 153 | }, 154 | "scripts": { 155 | "clean": "aegir clean", 156 | "lint": "aegir lint", 157 | "dep-check": "aegir dep-check -i protons", 158 | "test": "aegir test", 159 | "test:node": "aegir test -t node", 160 | "test:chrome": "aegir test -t browser", 161 | "test:chrome-webworker": "aegir test -t webworker", 162 | "test:firefox": "aegir test -t browser -- --browser firefox", 163 | "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", 164 | "build": "aegir build", 165 | "generate": "protons ./src/record.proto", 166 | "release": "aegir release", 167 | "docs": "aegir docs" 168 | }, 169 | "dependencies": { 170 | "@libp2p/interface-dht": "^2.0.0", 171 | "@libp2p/interfaces": "^3.2.0", 172 | "multiformats": "^11.0.0", 173 | "protons-runtime": "^5.0.0", 174 | "uint8arraylist": "^2.1.1", 175 | "uint8arrays": "^4.0.2" 176 | }, 177 | "devDependencies": { 178 | "@libp2p/crypto": "^1.0.11", 179 | "aegir": "^39.0.10", 180 | "protons": "^7.0.2" 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Record 3 | } from './record.js' 4 | import * as utils from './utils.js' 5 | import type { Uint8ArrayList } from 'uint8arraylist' 6 | 7 | export class Libp2pRecord { 8 | public key: Uint8Array 9 | public value: Uint8Array 10 | public timeReceived: Date 11 | 12 | constructor (key: Uint8Array, value: Uint8Array, timeReceived: Date) { 13 | if (!(key instanceof Uint8Array)) { 14 | throw new Error('key must be a Uint8Array') 15 | } 16 | 17 | if (!(value instanceof Uint8Array)) { 18 | throw new Error('value must be a Uint8Array') 19 | } 20 | 21 | this.key = key 22 | this.value = value 23 | this.timeReceived = timeReceived 24 | } 25 | 26 | serialize (): Uint8Array { 27 | return Record.encode(this.prepareSerialize()) 28 | } 29 | 30 | /** 31 | * Return the object format ready to be given to the protobuf library. 32 | */ 33 | prepareSerialize (): Record { 34 | return { 35 | key: this.key, 36 | value: this.value, 37 | timeReceived: utils.toRFC3339(this.timeReceived) 38 | } 39 | } 40 | 41 | /** 42 | * Decode a protobuf encoded record 43 | */ 44 | static deserialize (raw: Uint8Array | Uint8ArrayList): Libp2pRecord { 45 | const rec = Record.decode(raw) 46 | 47 | return new Libp2pRecord(rec.key, rec.value, new Date(rec.timeReceived)) 48 | } 49 | 50 | /** 51 | * Create a record from the raw object returned from the protobuf library 52 | */ 53 | static fromDeserialized (obj: Record): Libp2pRecord { 54 | const recvtime = utils.parseRFC3339(obj.timeReceived) 55 | 56 | if (obj.key == null) { 57 | throw new Error('key missing from deserialized object') 58 | } 59 | 60 | if (obj.value == null) { 61 | throw new Error('value missing from deserialized object') 62 | } 63 | 64 | const rec = new Libp2pRecord( 65 | obj.key, obj.value, recvtime 66 | ) 67 | 68 | return rec 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/record.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Record represents a dht record that contains a value 4 | // for a key value pair 5 | message Record { 6 | // The key that references this record 7 | bytes key = 1; 8 | 9 | // The actual value this record is storing 10 | bytes value = 2; 11 | 12 | // Note: These fields were removed from the Record message 13 | // hash of the authors public key 14 | // optional bytes author = 3; 15 | // A PKI signature for the key+value+author 16 | // optional bytes signature = 4; 17 | 18 | // Time the record was received, set by receiver 19 | string timeReceived = 5; 20 | } 21 | -------------------------------------------------------------------------------- /src/record.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/export */ 2 | /* eslint-disable complexity */ 3 | /* eslint-disable @typescript-eslint/no-namespace */ 4 | /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ 5 | /* eslint-disable @typescript-eslint/no-empty-interface */ 6 | 7 | import { encodeMessage, decodeMessage, message } from 'protons-runtime' 8 | import type { Codec } from 'protons-runtime' 9 | import type { Uint8ArrayList } from 'uint8arraylist' 10 | 11 | export interface Record { 12 | key: Uint8Array 13 | value: Uint8Array 14 | timeReceived: string 15 | } 16 | 17 | export namespace Record { 18 | let _codec: Codec 19 | 20 | export const codec = (): Codec => { 21 | if (_codec == null) { 22 | _codec = message((obj, w, opts = {}) => { 23 | if (opts.lengthDelimited !== false) { 24 | w.fork() 25 | } 26 | 27 | if ((obj.key != null && obj.key.byteLength > 0)) { 28 | w.uint32(10) 29 | w.bytes(obj.key) 30 | } 31 | 32 | if ((obj.value != null && obj.value.byteLength > 0)) { 33 | w.uint32(18) 34 | w.bytes(obj.value) 35 | } 36 | 37 | if ((obj.timeReceived != null && obj.timeReceived !== '')) { 38 | w.uint32(42) 39 | w.string(obj.timeReceived) 40 | } 41 | 42 | if (opts.lengthDelimited !== false) { 43 | w.ldelim() 44 | } 45 | }, (reader, length) => { 46 | const obj: any = { 47 | key: new Uint8Array(0), 48 | value: new Uint8Array(0), 49 | timeReceived: '' 50 | } 51 | 52 | const end = length == null ? reader.len : reader.pos + length 53 | 54 | while (reader.pos < end) { 55 | const tag = reader.uint32() 56 | 57 | switch (tag >>> 3) { 58 | case 1: 59 | obj.key = reader.bytes() 60 | break 61 | case 2: 62 | obj.value = reader.bytes() 63 | break 64 | case 5: 65 | obj.timeReceived = reader.string() 66 | break 67 | default: 68 | reader.skipType(tag & 7) 69 | break 70 | } 71 | } 72 | 73 | return obj 74 | }) 75 | } 76 | 77 | return _codec 78 | } 79 | 80 | export const encode = (obj: Partial): Uint8Array => { 81 | return encodeMessage(obj, Record.codec()) 82 | } 83 | 84 | export const decode = (buf: Uint8Array | Uint8ArrayList): Record => { 85 | return decodeMessage(buf, Record.codec()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/selectors.ts: -------------------------------------------------------------------------------- 1 | import { CodeError } from '@libp2p/interfaces/errors' 2 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string' 3 | import type { Selectors } from '@libp2p/interface-dht' 4 | 5 | /** 6 | * Select the best record out of the given records 7 | */ 8 | export function bestRecord (selectors: Selectors, k: Uint8Array, records: Uint8Array[]): number { 9 | if (records.length === 0) { 10 | const errMsg = 'No records given' 11 | 12 | throw new CodeError(errMsg, 'ERR_NO_RECORDS_RECEIVED') 13 | } 14 | 15 | const kStr = uint8ArrayToString(k) 16 | const parts = kStr.split('/') 17 | 18 | if (parts.length < 3) { 19 | const errMsg = 'Record key does not have a selector function' 20 | 21 | throw new CodeError(errMsg, 'ERR_NO_SELECTOR_FUNCTION_FOR_RECORD_KEY') 22 | } 23 | 24 | const selector = selectors[parts[1].toString()] 25 | 26 | if (selector == null) { 27 | const errMsg = `Unrecognized key prefix: ${parts[1]}` 28 | 29 | throw new CodeError(errMsg, 'ERR_UNRECOGNIZED_KEY_PREFIX') 30 | } 31 | 32 | if (records.length === 1) { 33 | return 0 34 | } 35 | 36 | return selector(k, records) 37 | } 38 | 39 | /** 40 | * Best record selector, for public key records. 41 | * Simply returns the first record, as all valid public key 42 | * records are equal 43 | */ 44 | function publickKey (k: Uint8Array, records: Uint8Array[]): number { 45 | return 0 46 | } 47 | 48 | export const selectors: Selectors = { 49 | pk: publickKey 50 | } 51 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a JavaScript date into an `RFC3339Nano` formatted 3 | * string 4 | */ 5 | export function toRFC3339 (time: Date): string { 6 | const year = time.getUTCFullYear() 7 | const month = String(time.getUTCMonth() + 1).padStart(2, '0') 8 | const day = String(time.getUTCDate()).padStart(2, '0') 9 | const hour = String(time.getUTCHours()).padStart(2, '0') 10 | const minute = String(time.getUTCMinutes()).padStart(2, '0') 11 | const seconds = String(time.getUTCSeconds()).padStart(2, '0') 12 | const milliseconds = time.getUTCMilliseconds() 13 | const nanoseconds = String(milliseconds * 1000 * 1000).padStart(9, '0') 14 | 15 | return `${year}-${month}-${day}T${hour}:${minute}:${seconds}.${nanoseconds}Z` 16 | } 17 | 18 | /** 19 | * Parses a date string formatted as `RFC3339Nano` into a 20 | * JavaScript Date object 21 | */ 22 | export function parseRFC3339 (time: string): Date { 23 | const rfc3339Matcher = new RegExp( 24 | // 2006-01-02T 25 | '(\\d{4})-(\\d{2})-(\\d{2})T' + 26 | // 15:04:05 27 | '(\\d{2}):(\\d{2}):(\\d{2})' + 28 | // .999999999Z 29 | '\\.(\\d+)Z' 30 | ) 31 | const m = String(time).trim().match(rfc3339Matcher) 32 | 33 | if (m == null) { 34 | throw new Error('Invalid format') 35 | } 36 | 37 | const year = parseInt(m[1], 10) 38 | const month = parseInt(m[2], 10) - 1 39 | const date = parseInt(m[3], 10) 40 | const hour = parseInt(m[4], 10) 41 | const minute = parseInt(m[5], 10) 42 | const second = parseInt(m[6], 10) 43 | const millisecond = parseInt(m[7].slice(0, -6), 10) 44 | 45 | return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond)) 46 | } 47 | -------------------------------------------------------------------------------- /src/validators.ts: -------------------------------------------------------------------------------- 1 | import { CodeError } from '@libp2p/interfaces/errors' 2 | import { sha256 } from 'multiformats/hashes/sha2' 3 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals' 4 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string' 5 | import type { Libp2pRecord } from './index.js' 6 | import type { Validators } from '@libp2p/interface-dht' 7 | 8 | /** 9 | * Checks a record and ensures it is still valid. 10 | * It runs the needed validators. 11 | * If verification fails the returned Promise will reject with the error. 12 | */ 13 | export async function verifyRecord (validators: Validators, record: Libp2pRecord): Promise { 14 | const key = record.key 15 | const keyString = uint8ArrayToString(key) 16 | const parts = keyString.split('/') 17 | 18 | if (parts.length < 3) { 19 | // No validator available 20 | return 21 | } 22 | 23 | const validator = validators[parts[1].toString()] 24 | 25 | if (validator == null) { 26 | const errMsg = 'Invalid record keytype' 27 | 28 | throw new CodeError(errMsg, 'ERR_INVALID_RECORD_KEY_TYPE') 29 | } 30 | 31 | await validator(key, record.value) 32 | } 33 | 34 | /** 35 | * Validator for public key records. 36 | * Verifies that the passed in record value is the PublicKey 37 | * that matches the passed in key. 38 | * If validation fails the returned Promise will reject with the error. 39 | * 40 | * @param {Uint8Array} key - A valid key is of the form `'/pk/'` 41 | * @param {Uint8Array} publicKey - The public key to validate against (protobuf encoded). 42 | */ 43 | const validatePublicKeyRecord = async (key: Uint8Array, publicKey: Uint8Array): Promise => { 44 | if (!(key instanceof Uint8Array)) { 45 | throw new CodeError('"key" must be a Uint8Array', 'ERR_INVALID_RECORD_KEY_NOT_BUFFER') 46 | } 47 | 48 | if (key.byteLength < 5) { 49 | throw new CodeError('invalid public key record', 'ERR_INVALID_RECORD_KEY_TOO_SHORT') 50 | } 51 | 52 | const prefix = uint8ArrayToString(key.subarray(0, 4)) 53 | 54 | if (prefix !== '/pk/') { 55 | throw new CodeError('key was not prefixed with /pk/', 'ERR_INVALID_RECORD_KEY_BAD_PREFIX') 56 | } 57 | 58 | const keyhash = key.slice(4) 59 | 60 | const publicKeyHash = await sha256.digest(publicKey) 61 | 62 | if (!uint8ArrayEquals(keyhash, publicKeyHash.bytes)) { 63 | throw new CodeError('public key does not match passed in key', 'ERR_INVALID_RECORD_HASH_MISMATCH') 64 | } 65 | } 66 | 67 | export const validators: Validators = { 68 | pk: validatePublicKeyRecord 69 | } 70 | -------------------------------------------------------------------------------- /test/fixtures/go-key-records.ts: -------------------------------------------------------------------------------- 1 | import { base64pad } from 'multiformats/bases/base64' 2 | 3 | export const publicKey = base64pad.decode( 4 | 'MCAASXjBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDjXAQQMal4SB2tSnX6NJIPmC69/BT8A8jc7/gDUZNkEhdhYHvc7k7S4vntV/c92nJGxNdop9fKJyevuNMuXhhHAgMBAAE=' 5 | ) 6 | -------------------------------------------------------------------------------- /test/fixtures/go-record.ts: -------------------------------------------------------------------------------- 1 | import { base16 } from 'multiformats/bases/base16' 2 | 3 | // Fixtures generated using gore (https://github.com/motemen/gore) 4 | // 5 | // :import github.com/libp2p/go-libp2p-record 6 | // :import github.com/libp2p/go-libp2p-crypto 7 | // 8 | // priv, pub, err := crypto.GenerateKeyPair(crypto.RSA, 1024) 9 | // 10 | // rec, err := record.MakePutRecord(priv, "hello", []byte("world"), false) 11 | // rec2, err := recordd.MakePutRecord(priv, "hello", []byte("world"), true) 12 | // 13 | // :import github.com/gogo/protobuf/proto 14 | // enc, err := proto.Marshal(rec) 15 | // enc2, err := proto.Marshal(rec2) 16 | // 17 | // :import io/ioutil 18 | // ioutil.WriteFile("js-libp2p-record/test/fixtures/record.bin", enc, 0644) 19 | // ioutil.WriteFile("js-libp2p-record/test/fixtures/record-signed.bin", enc2, 0644) 20 | export const serialized = base16.decode( 21 | 'f0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116' 22 | ) 23 | 24 | export const serializedSigned = base16.decode( 25 | 'f0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116228001500fe7505698b8a873ccde6f1d36a2be662d57807490d9a9959540f2645a454bf615215092e10123f6ffc4ed694711bfbb1d5ccb62f3da83cf4528ee577a96b6cf0272eef9a920bd56459993690060353b72c22b8c03ad2a33894522dac338905b201179a85cb5e2fc68ed58be96cf89beec6dc0913887dddc10f202a2a1b117' 26 | ) 27 | -------------------------------------------------------------------------------- /test/record.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { expect } from 'aegir/chai' 3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 4 | import { Libp2pRecord } from '../src/index.js' 5 | import * as fixture from './fixtures/go-record.js' 6 | 7 | const date = new Date() 8 | 9 | describe('record', () => { 10 | it('new', () => { 11 | const rec = new Libp2pRecord( 12 | uint8ArrayFromString('hello'), 13 | uint8ArrayFromString('world'), 14 | new Date() 15 | ) 16 | 17 | expect(rec).to.have.property('key').eql(uint8ArrayFromString('hello')) 18 | expect(rec).to.have.property('value').eql(uint8ArrayFromString('world')) 19 | }) 20 | 21 | it('serialize & deserialize', () => { 22 | const rec = new Libp2pRecord(uint8ArrayFromString('hello'), uint8ArrayFromString('world'), date) 23 | const dec = Libp2pRecord.deserialize(rec.serialize()) 24 | 25 | expect(dec).to.have.property('key').eql(uint8ArrayFromString('hello')) 26 | expect(dec).to.have.property('value').eql(uint8ArrayFromString('world')) 27 | expect(dec.timeReceived).to.be.eql(date) 28 | }) 29 | 30 | it('serialize & deserialize with padding', () => { 31 | // m/d/h/m/s/ms all need padding with 0s when converted to RFC3339 format 32 | const date = new Date('2022-04-03T01:04:08.078Z') 33 | 34 | const rec = new Libp2pRecord(uint8ArrayFromString('hello'), uint8ArrayFromString('world'), date) 35 | const dec = Libp2pRecord.deserialize(rec.serialize()) 36 | 37 | expect(dec).to.have.property('key').eql(uint8ArrayFromString('hello')) 38 | expect(dec).to.have.property('value').eql(uint8ArrayFromString('world')) 39 | expect(dec.timeReceived).to.be.eql(date) 40 | }) 41 | 42 | describe('go interop', () => { 43 | it('no signature', () => { 44 | const dec = Libp2pRecord.deserialize(fixture.serialized) 45 | expect(dec).to.have.property('key').eql(uint8ArrayFromString('hello')) 46 | expect(dec).to.have.property('value').eql(uint8ArrayFromString('world')) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /test/selection.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint max-nested-callbacks: ["error", 8] */ 2 | /* eslint-env mocha */ 3 | 4 | import { expect } from 'aegir/chai' 5 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 6 | import * as selection from '../src/selectors.js' 7 | import type { Selectors } from '@libp2p/interface-dht' 8 | 9 | const records = [new Uint8Array(), uint8ArrayFromString('hello')] 10 | 11 | describe('selection', () => { 12 | describe('bestRecord', () => { 13 | it('throws no records given when no records received', () => { 14 | expect( 15 | () => selection.bestRecord({}, uint8ArrayFromString('/'), []) 16 | ).to.throw( 17 | /No records given/ 18 | ) 19 | }) 20 | 21 | it('throws on missing selector in the record key', () => { 22 | expect( 23 | () => selection.bestRecord({}, uint8ArrayFromString('/'), records) 24 | ).to.throw( 25 | /Record key does not have a selector function/ 26 | ) 27 | }) 28 | 29 | it('throws on unknown key prefix', () => { 30 | expect( 31 | // @ts-expect-error invalid input 32 | () => selection.bestRecord({ world () {} }, uint8ArrayFromString('/hello/'), records) 33 | ).to.throw( 34 | /Unrecognized key prefix: hello/ 35 | ) 36 | }) 37 | 38 | it('returns the index from the matching selector', () => { 39 | const selectors: Selectors = { 40 | hello (k, recs) { 41 | expect(k).to.be.eql(uint8ArrayFromString('/hello/world')) 42 | expect(recs).to.be.eql(records) 43 | 44 | return 1 45 | } 46 | } 47 | 48 | expect( 49 | selection.bestRecord(selectors, uint8ArrayFromString('/hello/world'), records) 50 | ).to.equal( 51 | 1 52 | ) 53 | }) 54 | }) 55 | 56 | describe('selectors', () => { 57 | it('public key', () => { 58 | expect( 59 | selection.selectors.pk(uint8ArrayFromString('/hello/world'), records) 60 | ).to.equal( 61 | 0 62 | ) 63 | }) 64 | 65 | it('returns the first record when there is only one to select', () => { 66 | expect( 67 | selection.selectors.pk(uint8ArrayFromString('/hello/world'), [records[0]]) 68 | ).to.equal( 69 | 0 70 | ) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/utils.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { expect } from 'aegir/chai' 4 | import * as utils from '../src/utils.js' 5 | 6 | const dates = [{ 7 | obj: new Date(Date.UTC(2016, 0, 1, 8, 22, 33, 392)), 8 | str: '2016-01-01T08:22:33.392000000Z' 9 | }, { 10 | obj: new Date(Date.UTC(2016, 11, 30, 20, 2, 3, 392)), 11 | str: '2016-12-30T20:02:03.392000000Z' 12 | }, { 13 | obj: new Date(Date.UTC(2016, 11, 30, 20, 2, 5, 297)), 14 | str: '2016-12-30T20:02:05.297000000Z' 15 | }, { 16 | obj: new Date(Date.UTC(2012, 1, 25, 10, 10, 10, 10)), 17 | str: '2012-02-25T10:10:10.010000000Z' 18 | }] 19 | 20 | describe('utils', () => { 21 | it('toRFC3339', () => { 22 | dates.forEach((c) => { 23 | expect(utils.toRFC3339(c.obj)).to.be.eql(c.str) 24 | }) 25 | }) 26 | 27 | it('parseRFC3339', () => { 28 | dates.forEach((c) => { 29 | expect(utils.parseRFC3339(c.str)).to.be.eql(c.obj) 30 | }) 31 | }) 32 | 33 | it('to and from RFC3339', () => { 34 | dates.forEach((c) => { 35 | expect( 36 | utils.parseRFC3339(utils.toRFC3339(c.obj)) 37 | ).to.be.eql( 38 | c.obj 39 | ) 40 | expect( 41 | utils.toRFC3339(utils.parseRFC3339(c.str)) 42 | ).to.be.eql( 43 | c.str 44 | ) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/validator.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint max-nested-callbacks: ["error", 8] */ 2 | /* eslint-env mocha */ 3 | 4 | import { generateKeyPair, unmarshalPublicKey } from '@libp2p/crypto/keys' 5 | import { expect } from 'aegir/chai' 6 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 7 | import { Libp2pRecord } from '../src/index.js' 8 | import * as validator from '../src/validators.js' 9 | import * as fixture from './fixtures/go-key-records.js' 10 | import type { Validators } from '@libp2p/interface-dht' 11 | 12 | interface Cases { 13 | valid: { 14 | publicKey: Uint8Array[] 15 | } 16 | invalid: { 17 | publicKey: Array<{ 18 | data: Uint8Array 19 | code: string 20 | }> 21 | } 22 | } 23 | 24 | const generateCases = (hash: Uint8Array): Cases => { 25 | return { 26 | valid: { 27 | publicKey: [ 28 | Uint8Array.of( 29 | ...uint8ArrayFromString('/pk/'), 30 | ...hash 31 | ) 32 | ] 33 | }, 34 | invalid: { 35 | publicKey: [{ 36 | data: uint8ArrayFromString('/pk/'), 37 | code: 'ERR_INVALID_RECORD_KEY_TOO_SHORT' 38 | }, { 39 | data: Uint8Array.of(...uint8ArrayFromString('/pk/'), ...uint8ArrayFromString('random')), 40 | code: 'ERR_INVALID_RECORD_HASH_MISMATCH' 41 | }, { 42 | data: hash, 43 | code: 'ERR_INVALID_RECORD_KEY_BAD_PREFIX' 44 | }, { 45 | // @ts-expect-error invalid input 46 | data: 'not a buffer', 47 | code: 'ERR_INVALID_RECORD_KEY_NOT_BUFFER' 48 | }] 49 | } 50 | } 51 | } 52 | 53 | describe('validator', () => { 54 | let key: any 55 | let hash: Uint8Array 56 | let cases: Cases 57 | 58 | before(async () => { 59 | key = await generateKeyPair('RSA', 1024) 60 | hash = await key.public.hash() 61 | cases = generateCases(hash) 62 | }) 63 | 64 | describe('verifyRecord', () => { 65 | it('calls matching validator', async () => { 66 | const k = uint8ArrayFromString('/hello/you') 67 | const rec = new Libp2pRecord(k, uint8ArrayFromString('world'), new Date()) 68 | 69 | const validators: Validators = { 70 | async hello (key, value) { 71 | expect(key).to.eql(k) 72 | expect(value).to.eql(uint8ArrayFromString('world')) 73 | } 74 | } 75 | await validator.verifyRecord(validators, rec) 76 | }) 77 | 78 | it('calls not matching any validator', async () => { 79 | const k = uint8ArrayFromString('/hallo/you') 80 | const rec = new Libp2pRecord(k, uint8ArrayFromString('world'), new Date()) 81 | 82 | const validators: Validators = { 83 | async hello (key, value) { 84 | expect(key).to.eql(k) 85 | expect(value).to.eql(uint8ArrayFromString('world')) 86 | } 87 | } 88 | await expect(validator.verifyRecord(validators, rec)) 89 | .to.eventually.rejectedWith( 90 | /Invalid record keytype/ 91 | ) 92 | }) 93 | }) 94 | 95 | describe('validators', () => { 96 | it('exports pk', () => { 97 | expect(validator.validators).to.have.keys(['pk']) 98 | }) 99 | 100 | describe('public key', () => { 101 | it('exports func', () => { 102 | const pk = validator.validators.pk 103 | 104 | expect(pk).to.be.a('function') 105 | }) 106 | 107 | it('does not error on valid record', async () => { 108 | return Promise.all(cases.valid.publicKey.map(async (k) => { 109 | await validator.validators.pk(k, key.public.bytes) 110 | })) 111 | }) 112 | 113 | it('throws on invalid records', async () => { 114 | return Promise.all(cases.invalid.publicKey.map(async ({ data, code }) => { 115 | try { 116 | // 117 | await validator.validators.pk(data, key.public.bytes) 118 | } catch (err: any) { 119 | expect(err.code).to.eql(code) 120 | return 121 | } 122 | expect.fail('did not throw an error with code ' + code) 123 | })) 124 | }) 125 | }) 126 | }) 127 | 128 | describe('go interop', () => { 129 | it('record with key from from go', async () => { 130 | const pubKey = unmarshalPublicKey(fixture.publicKey) 131 | 132 | const hash = await pubKey.hash() 133 | const k = Uint8Array.of(...uint8ArrayFromString('/pk/'), ...hash) 134 | await validator.validators.pk(k, pubKey.bytes) 135 | }) 136 | }) 137 | }) 138 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "aegir/src/config/tsconfig.aegir.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": [ 7 | "src", 8 | "test" 9 | ] 10 | } 11 | --------------------------------------------------------------------------------