Skip to content

Commit

Permalink
Merge branch 'main' into feat/storage-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
AmeanAsad authored Jan 19, 2024
2 parents e2f2d02 + 17abad7 commit 69a9f74
Show file tree
Hide file tree
Showing 8 changed files with 937 additions and 6 deletions.
806 changes: 806 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@
"p-limit": "^5.0.0"
},
"devDependencies": {
"crypto-browserify": "^3.12.0",
"eslint": "^8.24.0",
"eslint-config-ipfs": "^3.1.1",
"eslint-plugin-jsdoc": "^39.3.6",
"eslint-plugin-promise": "^6.0.1",
"stream-browserify": "^3.0.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
Expand All @@ -63,4 +65,4 @@
"publishConfig": {
"access": "public"
}
}
}
8 changes: 7 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export class Saturn {
if (!opts.format) {
yield * itr
} else {
yield * extractVerifiedContent(cidPath, itr)
yield * extractVerifiedContent(cidPath, itr, opts.range || {})
}
} catch (err) {
log.error = err.message
Expand Down Expand Up @@ -436,6 +436,7 @@ export class Saturn {
* @param {string} [opts.format]
* @param {string} [opts.originFallback]
* @param {object} [opts.jwt]
* @param {import('./types.js').ContentRange} [opts.range]
* @returns {URL}
*/
createRequestURL (cidPath, opts = {}) {
Expand All @@ -456,6 +457,11 @@ export class Saturn {
url.searchParams.set('jwt', opts.jwt)
}

if (typeof opts.range === 'object' && (opts.range.rangeStart || opts.range.rangeEnd)) {
const rangeStart = opts.range.rangeStart?.toString() || '0'
const rangeEnd = opts.range.rangeEnd?.toString() || '*'
url.searchParams.set('entity-bytes', rangeStart + ':' + rangeEnd)
}
return url
}

Expand Down
10 changes: 10 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @typedef {object} FetchOptions
* @property {Node[]} [nodes] - An array of nodes.
* @property {('car'|'raw')} [format] - The format of the fetched content.
*
* @property {boolean} [originFallback] - Is this a fallback to the customer origin
* @property {boolean} [raceNodes] - Does the fetch race multiple nodes on requests.
* @property {string} [customerFallbackURL] - Customer Origin that is a fallback.
Expand All @@ -26,6 +27,15 @@
* @property {number} [downloadTimeout=0] - Download timeout in milliseconds.
* @property {AbortController} [controller] - Abort controller
* @property {boolean} [firstHitDNS] - First request hit is always to CDN origin.
* @property {ContentRange} [range] - range to fetch, compatible with entity bytes parameter
*/

/**
* Options for a range request
*
* @typedef {object} ContentRange
* @property {number} [rangeStart]
* @property {number} [rangeEnd]
*/

export {}
41 changes: 37 additions & 4 deletions src/utils/car.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CarBlockIterator } from '@ipld/car'
import * as dagCbor from '@ipld/dag-cbor'
import * as dagPb from '@ipld/dag-pb'
import * as dagJson from '@ipld/dag-json'
import { exporter } from 'ipfs-unixfs-exporter'
import * as unixfs from 'ipfs-unixfs-exporter'
import { bytes } from 'multiformats'
import * as raw from 'multiformats/codecs/raw'
import * as json from 'multiformats/codecs/json'
Expand Down Expand Up @@ -89,12 +89,45 @@ export async function verifyBlock (cid, bytes) {
*
* @param {string} cidPath
* @param {ReadableStream|AsyncIterable} carStream
* @param {import('../types.js').ContentRange} options
*/
export async function * extractVerifiedContent (cidPath, carStream) {
export async function * extractVerifiedContent (cidPath, carStream, options = {}) {
const getter = await CarBlockGetter.fromStream(carStream)
const node = await exporter(cidPath, getter)
const node = await unixfs.exporter(cidPath, getter)

for await (const chunk of node.content()) {
for await (const chunk of contentGenerator(node, options)) {
yield chunk
}
}

/**
*
* @param {unixfs.UnixFSEntry} node
* @param {import('../types.js').ContentRange} options
*/
function contentGenerator(node, options = {}) {

let rangeStart = options.rangeStart ?? 0
if (rangeStart < 0) {
rangeStart = rangeStart + Number(node.size)
if (rangeStart < 0) {
rangeStart = 0
}

}
if (options.rangeEnd === null || options.rangeEnd === undefined) {
return node.content({offset: rangeStart})
}

let rangeEnd = options.rangeEnd
if (rangeEnd < 0) {
rangeEnd = rangeEnd + Number(node.size)
} else {
rangeEnd = rangeEnd+1
}
const toRead = rangeEnd - rangeStart
if (toRead < 0) {
throw new Error("range end must be greater than range start")
}
return node.content({offset: rangeStart, length: toRead})
}
56 changes: 56 additions & 0 deletions test/car.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,62 @@ describe('CAR Verification', () => {
assert.strictEqual(actualContent, expectedContent)
})

it('should extract content from a valid CAR with a range', async () => {
const cidPath =
'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4'
const filepath = getFixturePath('hello.car')
const carStream = fs.createReadStream(filepath)

const contentItr = await extractVerifiedContent(cidPath, carStream, {rangeStart: 1, rangeEnd: 3})
const buffer = await concatChunks(contentItr)
const actualContent = String.fromCharCode(...buffer)
const expectedContent = 'ell'

assert.strictEqual(actualContent, expectedContent)
})

it('should extract content from a valid CAR with a range with only a start', async () => {
const cidPath =
'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4'
const filepath = getFixturePath('hello.car')
const carStream = fs.createReadStream(filepath)

const contentItr = await extractVerifiedContent(cidPath, carStream, {rangeStart: 1})
const buffer = await concatChunks(contentItr)
const actualContent = String.fromCharCode(...buffer)
const expectedContent = 'ello world\n'

assert.strictEqual(actualContent, expectedContent)
})

it('should extract content from a valid CAR with a range with a negative end', async () => {
const cidPath =
'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4'
const filepath = getFixturePath('hello.car')
const carStream = fs.createReadStream(filepath)

const contentItr = await extractVerifiedContent(cidPath, carStream, {rangeStart: 1, rangeEnd: -1})
const buffer = await concatChunks(contentItr)
const actualContent = String.fromCharCode(...buffer)
const expectedContent = 'ello world'

assert.strictEqual(actualContent, expectedContent)
})

it('should extract content from a valid CAR with a range with a negative start and end', async () => {
const cidPath =
'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4'
const filepath = getFixturePath('hello.car')
const carStream = fs.createReadStream(filepath)

const contentItr = await extractVerifiedContent(cidPath, carStream, {rangeStart: -5, rangeEnd: -1})
const buffer = await concatChunks(contentItr)
const actualContent = String.fromCharCode(...buffer)
const expectedContent = 'orld'

assert.strictEqual(actualContent, expectedContent)
})

it('should verify intermediate path segments', async () => {
const cidPath =
'bafybeigeqgfwhivuuxgmuvcrrwvs4j3yfzgljssvnuqzokm6uby4fpmwsa/subdir/hello.txt'
Expand Down
12 changes: 12 additions & 0 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ describe('Saturn client', () => {
})
})

describe('Create a request URL', () => {
const client = new Saturn({ clientKey })
it('should translate entity bytes params', () => {
assert.strictEqual(client.createRequestURL('bafy...').searchParams.get('entity-bytes'), null)
assert.strictEqual(client.createRequestURL('bafy...', { range: {} }).searchParams.get('entity-bytes'), null)
assert.strictEqual(client.createRequestURL('bafy...', { range: { rangeStart: 0 } }).searchParams.get('entity-bytes'), null)
assert.strictEqual(client.createRequestURL('bafy...', { range: { rangeStart: 10 } }).searchParams.get('entity-bytes'), '10:*')
assert.strictEqual(client.createRequestURL('bafy...', { range: { rangeStart: 10, rangeEnd: 20 } }).searchParams.get('entity-bytes'), '10:20')
assert.strictEqual(client.createRequestURL('bafy...', { range: { rangeEnd: 20 } }).searchParams.get('entity-bytes'), '0:20')
})
})

describe('Logging', () => {
const handlers = [
mockJWT(TEST_AUTH)
Expand Down
6 changes: 6 additions & 0 deletions webpack.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ module.exports = {
name: 'SaturnModule',
type: 'var',
}
},
resolve: {
fallback: {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify')
}
}
};

0 comments on commit 69a9f74

Please sign in to comment.