@helia/ipns 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +59 -0
  3. package/dist/index.min.js +25 -0
  4. package/dist/src/index.d.ts +124 -0
  5. package/dist/src/index.d.ts.map +1 -0
  6. package/dist/src/index.js +192 -0
  7. package/dist/src/index.js.map +1 -0
  8. package/dist/src/routing/dht.d.ts +18 -0
  9. package/dist/src/routing/dht.d.ts.map +1 -0
  10. package/dist/src/routing/dht.js +65 -0
  11. package/dist/src/routing/dht.js.map +1 -0
  12. package/dist/src/routing/index.d.ts +17 -0
  13. package/dist/src/routing/index.d.ts.map +1 -0
  14. package/dist/src/routing/index.js +3 -0
  15. package/dist/src/routing/index.js.map +1 -0
  16. package/dist/src/routing/local-store.d.ts +15 -0
  17. package/dist/src/routing/local-store.d.ts.map +1 -0
  18. package/dist/src/routing/local-store.js +48 -0
  19. package/dist/src/routing/local-store.js.map +1 -0
  20. package/dist/src/routing/pubsub.d.ts +20 -0
  21. package/dist/src/routing/pubsub.d.ts.map +1 -0
  22. package/dist/src/routing/pubsub.js +150 -0
  23. package/dist/src/routing/pubsub.js.map +1 -0
  24. package/dist/src/utils/resolve-dns-link.browser.d.ts +6 -0
  25. package/dist/src/utils/resolve-dns-link.browser.d.ts.map +1 -0
  26. package/dist/src/utils/resolve-dns-link.browser.js +46 -0
  27. package/dist/src/utils/resolve-dns-link.browser.js.map +1 -0
  28. package/dist/src/utils/resolve-dns-link.d.ts +3 -0
  29. package/dist/src/utils/resolve-dns-link.d.ts.map +1 -0
  30. package/dist/src/utils/resolve-dns-link.js +54 -0
  31. package/dist/src/utils/resolve-dns-link.js.map +1 -0
  32. package/dist/src/utils/tlru.d.ts +15 -0
  33. package/dist/src/utils/tlru.d.ts.map +1 -0
  34. package/dist/src/utils/tlru.js +39 -0
  35. package/dist/src/utils/tlru.js.map +1 -0
  36. package/package.json +191 -0
  37. package/src/index.ts +296 -0
  38. package/src/routing/dht.ts +85 -0
  39. package/src/routing/index.ts +26 -0
  40. package/src/routing/local-store.ts +63 -0
  41. package/src/routing/pubsub.ts +195 -0
  42. package/src/utils/resolve-dns-link.browser.ts +61 -0
  43. package/src/utils/resolve-dns-link.ts +65 -0
  44. package/src/utils/tlru.ts +52 -0
@@ -0,0 +1,61 @@
1
+ /* eslint-env browser */
2
+
3
+ import { TLRU } from './tlru.js'
4
+ import PQueue from 'p-queue'
5
+ import type { AbortOptions } from '@libp2p/interfaces'
6
+
7
+ // Avoid sending multiple queries for the same hostname by caching results
8
+ const cache = new TLRU<{ Path: string, Message: string }>(1000)
9
+ // TODO: /api/v0/dns does not return TTL yet: https://github.com/ipfs/go-ipfs/issues/5884
10
+ // However we know browsers themselves cache DNS records for at least 1 minute,
11
+ // which acts a provisional default ttl: https://stackoverflow.com/a/36917902/11518426
12
+ const ttl = 60 * 1000
13
+
14
+ // browsers limit concurrent connections per host,
15
+ // we don't want preload calls to exhaust the limit (~6)
16
+ const httpQueue = new PQueue({ concurrency: 4 })
17
+
18
+ const ipfsPath = (response: { Path: string, Message: string }): string => {
19
+ if (response.Path != null) {
20
+ return response.Path
21
+ }
22
+ throw new Error(response.Message)
23
+ }
24
+
25
+ export interface ResolveDnsLinkOptions extends AbortOptions {
26
+ nocache?: boolean
27
+ }
28
+
29
+ export async function resolveDnslink (fqdn: string, opts: ResolveDnsLinkOptions = {}): Promise<string> { // eslint-disable-line require-await
30
+ const resolve = async (fqdn: string, opts: ResolveDnsLinkOptions = {}): Promise<string> => {
31
+ // @ts-expect-error - URLSearchParams does not take boolean options, only strings
32
+ const searchParams = new URLSearchParams(opts)
33
+ searchParams.set('arg', fqdn)
34
+
35
+ // try cache first
36
+ const query = searchParams.toString()
37
+ if (opts.nocache !== true && cache.has(query)) {
38
+ const response = cache.get(query)
39
+
40
+ if (response != null) {
41
+ return ipfsPath(response)
42
+ }
43
+ }
44
+
45
+ // fallback to delegated DNS resolver
46
+ const response = await httpQueue.add(async () => {
47
+ // Delegated HTTP resolver sending DNSLink queries to ipfs.io
48
+ // TODO: replace hardcoded host with configurable DNS over HTTPS: https://github.com/ipfs/js-ipfs/issues/2212
49
+ const res = await fetch(`https://ipfs.io/api/v0/dns?${searchParams}`)
50
+ const query = new URL(res.url).search.slice(1)
51
+ const json = await res.json()
52
+ cache.set(query, json, ttl)
53
+
54
+ return json
55
+ })
56
+
57
+ return ipfsPath(response)
58
+ }
59
+
60
+ return await resolve(fqdn, opts)
61
+ }
@@ -0,0 +1,65 @@
1
+ import dns from 'dns'
2
+ import { promisify } from 'util'
3
+ import type { AbortOptions } from '@libp2p/interfaces'
4
+ import * as isIPFS from 'is-ipfs'
5
+
6
+ const MAX_RECURSIVE_DEPTH = 32
7
+
8
+ export async function resolveDnslink (domain: string, options: AbortOptions = {}): Promise<string> {
9
+ return await recursiveResolveDnslink(domain, MAX_RECURSIVE_DEPTH, options)
10
+ }
11
+
12
+ async function recursiveResolveDnslink (domain: string, depth: number, options: AbortOptions = {}): Promise<string> {
13
+ if (depth === 0) {
14
+ throw new Error('recursion limit exceeded')
15
+ }
16
+
17
+ let dnslinkRecord
18
+
19
+ try {
20
+ dnslinkRecord = await resolve(domain)
21
+ } catch (err: any) {
22
+ // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error
23
+ if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') {
24
+ throw err
25
+ }
26
+
27
+ if (domain.startsWith('_dnslink.')) {
28
+ // The supplied domain contains a _dnslink component
29
+ // Check the non-_dnslink domain
30
+ dnslinkRecord = await resolve(domain.replace('_dnslink.', ''))
31
+ } else {
32
+ // Check the _dnslink subdomain
33
+ const _dnslinkDomain = `_dnslink.${domain}`
34
+ // If this throws then we propagate the error
35
+ dnslinkRecord = await resolve(_dnslinkDomain)
36
+ }
37
+ }
38
+
39
+ const result = dnslinkRecord.replace('dnslink=', '')
40
+ const domainOrCID = result.split('/')[2]
41
+ const isIPFSCID = isIPFS.cid(domainOrCID)
42
+
43
+ if (isIPFSCID || depth === 0) {
44
+ return result
45
+ }
46
+
47
+ return await recursiveResolveDnslink(domainOrCID, depth - 1, options)
48
+ }
49
+
50
+ async function resolve (domain: string, options: AbortOptions = {}): Promise<string> {
51
+ const DNSLINK_REGEX = /^dnslink=.+$/
52
+ const records = await promisify(dns.resolveTxt)(domain)
53
+ const dnslinkRecords = records.reduce((rs, r) => rs.concat(r), [])
54
+ .filter(record => DNSLINK_REGEX.test(record))
55
+
56
+ const dnslinkRecord = dnslinkRecords[0]
57
+
58
+ // we now have dns text entries as an array of strings
59
+ // only records passing the DNSLINK_REGEX text are included
60
+ if (dnslinkRecord == null) {
61
+ throw new Error(`No dnslink records found for domain: ${domain}`)
62
+ }
63
+
64
+ return dnslinkRecord
65
+ }
@@ -0,0 +1,52 @@
1
+ import hashlru from 'hashlru'
2
+
3
+ /**
4
+ * Time Aware Least Recent Used Cache
5
+ *
6
+ * @see https://arxiv.org/pdf/1801.00390
7
+ */
8
+ export class TLRU<T> {
9
+ private readonly lru: ReturnType<typeof hashlru>
10
+
11
+ constructor (maxSize: number) {
12
+ this.lru = hashlru(maxSize)
13
+ }
14
+
15
+ get (key: string): T | undefined {
16
+ const value = this.lru.get(key)
17
+
18
+ if (value != null) {
19
+ if (value.expire != null && value.expire < Date.now()) {
20
+ this.lru.remove(key)
21
+
22
+ return undefined
23
+ }
24
+
25
+ return value.value
26
+ }
27
+
28
+ return undefined
29
+ }
30
+
31
+ set (key: string, value: T, ttl: number): void {
32
+ this.lru.set(key, { value, expire: Date.now() + ttl })
33
+ }
34
+
35
+ has (key: string): boolean {
36
+ const value = this.get(key)
37
+
38
+ if (value != null) {
39
+ return true
40
+ }
41
+
42
+ return false
43
+ }
44
+
45
+ remove (key: string): void {
46
+ this.lru.remove(key)
47
+ }
48
+
49
+ clear (): void {
50
+ this.lru.clear()
51
+ }
52
+ }