Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,12 @@
},
"dependencies": {
"blockstore-core": "^6.1.2",
"browser-readablestream-to-it": "^2.0.7",
"cachedir": "^2.3.0",
"delay": "^7.0.0",
"got": "^14.4.7",
"gunzip-maybe": "^1.4.2",
"ipfs-unixfs-importer": "^16.1.1",
"it-last": "^3.0.2",
"it-to-buffer": "^4.0.7",
"multiformats": "^13.1.0",
"p-retry": "^7.0.0",
"package-config": "^5.0.0",
"tar-fs": "^3.0.6",
"tar-stream": "^3.1.7",
Expand Down
67 changes: 7 additions & 60 deletions src/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ import os from 'node:os'
import path from 'node:path'
import * as url from 'node:url'
import util from 'node:util'
import browserReadableStreamToIt from 'browser-readablestream-to-it'
import cachedir from 'cachedir'
import delay from 'delay'
import got from 'got'
import gunzip from 'gunzip-maybe'
import toBuffer from 'it-to-buffer'
import { CID } from 'multiformats/cid'
import retry from 'p-retry'
import { packageConfigSync } from 'package-config'
import tarFS from 'tar-fs'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
Expand All @@ -33,20 +30,15 @@ const { latest, versions } = JSON.parse(fs.readFileSync(path.join(__dirname, 've
encoding: 'utf-8'
}))

const DOWNLOAD_TIMEOUT_MS = 60000

/**
* avoid expensive fetch if file is already in cache
*
* @param {string} url
* @param {string} cid
* @param {{ retries?: number, retryDelay?: number }} [options]
*/
async function cachingFetchAndVerify (url, cid, options = {}) {
async function cachingFetchAndVerify (url, cid) {
const cacheDir = process.env.NPM_GO_LIBP2P_CACHE || cachedir('npm-go-libp2p')
const filename = url.split('/').pop()
const retries = options.retries ?? 10
const retryDelay = options.retryDelay ?? 5000

if (!filename) {
throw new Error('Invalid URL')
Expand All @@ -62,42 +54,8 @@ async function cachingFetchAndVerify (url, cid, options = {}) {
console.info(`Cached file ${cachedFilePath} not found`)
console.info(`Downloading ${url} to ${cacheDir}`)

const buf = await retry(async () => {
const signal = AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)

try {
const res = await fetch(url, {
signal
})

console.info(`${url} ${res.status} ${res.statusText}`)

if (!res.ok) {
throw new Error(`${res.status}: ${res.statusText}`)
}

const body = res.body

if (body == null) {
throw new Error('Response had no body')
}

return await toBuffer(browserReadableStreamToIt(body))
} catch (err) {
if (signal.aborted) {
console.error(`Download timed out after ${DOWNLOAD_TIMEOUT_MS}ms`)
}

throw err
}
}, {
retries,
onFailedAttempt: async (err) => {
console.error('Attempt', err.attemptNumber, 'failed. There are', err.retriesLeft, 'retries left', err)
console.info('Waiting for', retryDelay / 1000, 'seconds before retrying')
await delay(retryDelay)
}
})
// use got rather than the global fetch, which can hang reading the response body in CI
const buf = await got(url).buffer()

// download file
fs.writeFileSync(cachedFilePath, buf)
Expand Down Expand Up @@ -153,8 +111,6 @@ function unpack (installPath, stream) {
* @param {string} [options.platform]
* @param {string} [options.arch]
* @param {string} [options.installPath]
* @param {number} [options.retries]
* @param {number} [options.retryDelay]
*/
function cleanArguments (options = {}) {
const conf = packageConfigSync('go-libp2p', {
Expand All @@ -170,9 +126,7 @@ function cleanArguments (options = {}) {
platform: process.env.TARGET_OS ?? options.platform ?? os.platform(),
arch: process.env.TARGET_ARCH ?? options.arch ?? goenv.GOARCH,
distUrl: process.env.GO_LIBP2P_DIST_URL ?? conf.distUrl,
installPath: options.installPath ? path.resolve(options.installPath) : process.cwd(),
retries: Number(process.env.RETRIES ?? options.retries ?? 10),
retryDelay: Number(process.env.RETRY_DELAY ?? options.retryDelay ?? 5000)
installPath: options.installPath ? path.resolve(options.installPath) : process.cwd()
}
}

Expand Down Expand Up @@ -224,15 +178,10 @@ async function getDownloadURL (version, platform, arch, distUrl) {
* @param {string} options.arch
* @param {string} options.installPath
* @param {string} options.distUrl
* @param {number} options.retries
* @param {number} options.retryDelay
*/
async function downloadFile ({ version, platform, arch, installPath, distUrl, retries, retryDelay }) {
async function downloadFile ({ version, platform, arch, installPath, distUrl }) {
const { cid, url } = await getDownloadURL(version, platform, arch, distUrl)
const data = await cachingFetchAndVerify(url, cid, {
retries,
retryDelay
})
const data = await cachingFetchAndVerify(url, cid)

await unpack(installPath, data)
console.info(`Unpacked ${installPath}`)
Expand Down Expand Up @@ -320,8 +269,6 @@ async function link ({ depBin }) {
* @param {string} [options.platform]
* @param {string} [options.arch]
* @param {string} [options.installPath]
* @param {number} [options.retries]
* @param {number} [options.retryDelay]
* @returns {Promise<string>}
*/
export async function download (options = {}) {
Expand Down
70 changes: 54 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import { resolve, join } from 'node:path'
import * as url from 'node:url'
const isWin = process.platform === 'win32'

const __dirname = url.fileURLToPath(new URL('.', import.meta.url))

const binaryLocations = isWin
? [
resolve(join(__dirname, '..', 'p2pd.exe')),
resolve(join(__dirname, '..', 'bin/p2pd.exe'))
]
: [
resolve(join(__dirname, '..', 'p2pd')),
resolve(join(__dirname, '..', 'bin/p2pd'))
]

/**
* @returns {string}
* @param {string} file
* @returns {boolean}
*/
export function path () {
const paths = isWin
? [
resolve(join(__dirname, '..', 'p2pd.exe')),
resolve(join(__dirname, '..', 'bin/p2pd.exe'))
]
: [
resolve(join(__dirname, '..', 'p2pd')),
resolve(join(__dirname, '..', 'bin/p2pd'))
]

for (const bin of paths) {
if (fs.existsSync(bin)) {
return bin
function isRealBinary (file) {
let fd
try {
fd = fs.openSync(file, 'r')
const header = Buffer.alloc(2)
fs.readSync(fd, header, 0, 2, 0)
return header.toString('latin1') !== '#!'
} catch {
return false
} finally {
if (fd != null) {
fs.closeSync(fd)
}
}
}

const findBinary = () => binaryLocations.find(isRealBinary)

function installBinary () {
execFileSync(process.execPath, [join(__dirname, 'post-install.js')], {
cwd: resolve(join(__dirname, '..')),
stdio: ['ignore', 2, 2]
})
}

/**
* @param {{ autoDownload?: boolean }} [options]
* @returns {string}
*/
export function path (options = {}) {
const { autoDownload = true } = options

let binary = findBinary()

if (binary == null && autoDownload) {
installBinary()
binary = findBinary()
}

if (binary == null) {
throw new Error('p2pd binary not found, it may not be installed or an error may have occurred during installation')
}

throw new Error('p2pd binary not found, it may not be installed or an error may have occurred during installation')
return binary
}
6 changes: 3 additions & 3 deletions test/download.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ describe('download', () => {
it('returns an error when dist url is 404', async () => {
process.env.GO_LIBP2P_DIST_URL = 'https://dist.ipfs.io/notfound'

await expect(download({ version: 'v0.4.0', retries: 0 })).to.eventually.be.rejected
await expect(download({ version: 'v0.4.0' })).to.eventually.be.rejected
.with.property('message').that.matches(/404/)

delete process.env.GO_LIBP2P_DIST_URL
})

it('path returns undefined when no binary has been downloaded', async () => {
expect(detectLocation).to.throw(/not found/, 'Path did not throw when binary is not installed')
it('path throws when no binary is installed and autoDownload is disabled', () => {
expect(() => detectLocation({ autoDownload: false })).to.throw(/not found/, 'Path did not throw when binary is not installed')
})
})
Loading