-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
124 lines (114 loc) · 4.15 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import fs from 'fs'
import path from 'path'
import fetch from 'isomorphic-unfetch'
import { bytes } from 'multiformats'
import { sha256 } from 'multiformats/hashes/sha2'
import { CarReader } from '@ipld/car'
import { recursive } from 'ipfs-unixfs-exporter'
import toIterable from 'stream-to-it'
import { pipe } from 'it-pipe'
const { toHex } = bytes
const hashes = {
[sha256.code]: sha256
}
export async function ipfsGet ({ ipfsPath, gateway, output, quiet }) {
console.log = quiet ? () => {} : console.log
const gatewayUrl = toUrl(gateway)
let cid = ipfsPath
if (ipfsPath.startsWith('/ipfs/')) {
cid = ipfsPath.substring('/ipfs/'.length)
}
if (cid.match('/')) {
// looks pathish, i.e CID/path! resolve to get the CID for the file in the path
console.log(`📡 Resolving CID from ${gatewayUrl}`)
cid = await resolveIpfsAddress(ipfsPath, gatewayUrl)
console.log(`🎯 ${cid}`)
// use the last chunk of the path as the output if not set
output = output || path.basename(ipfsPath)
}
console.log(`📡 Fetching .car file from ${gatewayUrl}`)
const carStream = await fetchCar(cid, gatewayUrl)
const carReader = await CarReader.fromIterable(carStream)
const { blockCount, filename } = await extractCar({ cid, carReader, output })
console.log(`🔐 Verified ${blockCount}/${blockCount} block${blockCount === 1 ? '' : 's'}`)
console.log(`✅ Wrote ${filename}`)
}
export async function extractCar ({ cid, carReader, output }) {
let blockCount = 0
const verifyingBlockService = {
get: async (cid) => {
const res = await carReader.get(cid)
if (!isValid(res)) {
throw new Error(`Bad block. Hash does not match CID ${cid}`)
}
blockCount++
return res.bytes
},
}
// magic extracted from js-ipfs:
// https://github.com/ipfs/js-ipfs/blob/46618c795bf5363ba3186645640fb81349231db7/packages/ipfs-core/src/components/get.js#L20
// https://github.com/ipfs/js-ipfs/blob/46618c795bf5363ba3186645640fb81349231db7/packages/ipfs-cli/src/commands/get.js#L56-L66
for await (const file of recursive(cid, verifyingBlockService, { /* options */ })) {
let filePath = file.path
// output overrides the first part of the path.
if (output) {
const parts = file.path.split('/')
parts[0] = output
filePath = parts.join('/')
}
if (file.type === 'directory') {
await fs.promises.mkdir(filePath, { recursive: true })
} else {
await pipe(
file.content,
toIterable.sink(fs.createWriteStream(filePath))
)
}
}
return { blockCount, filename: output || cid }
}
async function fetchCar(cid, gateway) {
const url = `${gateway}/ipfs/${cid}`
const res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/vnd.ipld.car',
},
})
if (res.status > 400) {
throw new Error(`${res.status} ${res.statusText} ${url}`)
}
return res.body
}
async function resolveIpfsAddress (ipfsPath, gateway) {
// $ curl -X POST "http://127.0.0.1:5001/api/v0/resolve?arg=bafybeidd2gyhagleh47qeg77xqndy2qy3yzn4vkxmk775bg2t5lpuy7pcu/dr-is-tired.jpg" 11:45:26
// {"Path":"/ipfs/bafkreiabltrd5zm73pvi7plq25pef3hm7jxhbi3kv4hapegrkfpkqtkbme"}
const url = `${gateway}api/v0/resolve?arg=${ipfsPath}`
const res = await fetch(url, { method: 'POST' })
if (res.status > 400) {
throw new Error(`${res.status} ${res.statusText} ${url}`)
}
const body = await res.json()
if (!body.Path) {
throw new Error(`Unexpected response from resolve ${JSON.stringify(body)}`)
}
if (!body.Path.startsWith('/ipfs/')) {
throw new Error(`Expected ${ipfsPath} to resolve to a CID but found ${body.Path}`)
}
return body.Path.substring('/ipfs/'.length)
}
async function isValid ({ cid, bytes }) {
const hashfn = hashes[cid.multihash.code]
if (!hashfn) {
throw new Error(`Missing hash function for ${cid.multihash.code}`)
}
const hash = await hashfn.digest(bytes)
return toHex(hash.digest) === toHex(cid.multihash.digest)
}
function toUrl (str) {
if (!str.match('://')) {
const scheme = ['localhost', '127.0.0.1'].includes(str) ? 'http' : 'https'
return toUrl(`${scheme}://${str}`)
}
return new URL(str)
}