Skip to content

Commit

Permalink
feat: resolve unzip using package.json browser field (#50)
Browse files Browse the repository at this point in the history
* feat: resolve unzip using package.json browser field

* chore: complete shim implementation

* feat: resolve fs-extra as empty for browser
  • Loading branch information
manzt authored Sep 24, 2021
1 parent 1eebda7 commit 983de5d
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 151 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,9 @@
},
"publishConfig": {
"access": "public"
},
"browser": {
"./dist/unzip.js": "./dist/unzip-pako.js",
"fs-extra": false
}
}
4 changes: 1 addition & 3 deletions src/localFile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const fs =
// eslint-disable-next-line camelcase
typeof __webpack_require__ !== 'function' ? require('fs-extra') : undefined
const fs = require('fs-extra')

// LocalFile is pretty much just an implementation of the node 10+ fs.promises filehandle,
// we can switch to that when the API is stable
Expand Down
156 changes: 156 additions & 0 deletions src/unzip-pako.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* eslint-disable consistent-return */
const { Z_SYNC_FLUSH, Inflate } = require('pako')

// browserify-zlib, which is the zlib shim used by default in webpacked code,
// does not properly uncompress bgzf chunks that contain more than
// one bgzf block, so export an unzip function that uses pako directly
// if we are running in a browser.
async function unzip(inputData) {
try {
let strm
let pos = 0
let i = 0
const chunks = []
let inflator
do {
const remainingInput = inputData.slice(pos)
inflator = new Inflate()
;({ strm } = inflator)
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

pos += strm.next_in
chunks[i] = Buffer.from(inflator.result)
i += 1
} while (strm.avail_in)

const result = Buffer.concat(chunks)
return result
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
}
}

// similar to pakounzip, except it does extra counting
// to return the positions of compressed and decompressed
// data offsets
async function unzipChunk(inputData) {
try {
let strm
let cpos = 0
let dpos = 0
const blocks = []
const cpositions = []
const dpositions = []
do {
const remainingInput = inputData.slice(cpos)
const inflator = new Inflate()
// @ts-ignore
;({ strm } = inflator)
// @ts-ignore
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

// @ts-ignore
const buffer = Buffer.from(inflator.result)
blocks.push(buffer)

cpositions.push(cpos)
dpositions.push(dpos)

cpos += strm.next_in
dpos += buffer.length
} while (strm.avail_in)

const buffer = Buffer.concat(blocks)
return { buffer, cpositions, dpositions }
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
}
}

// similar to unzipChunk above but slices (0,minv.dataPosition) and (maxv.dataPosition,end) off
async function unzipChunkSlice(inputData, chunk) {
try {
let strm
let cpos = chunk.minv.blockPosition
let dpos = chunk.minv.dataPosition
const decompressedBlocks = []
const cpositions = []
const dpositions = []
do {
const remainingInput = inputData.slice(cpos - chunk.minv.blockPosition)
const inflator = new Inflate()
// @ts-ignore
;({ strm } = inflator)
// @ts-ignore
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

// @ts-ignore
const buffer = Buffer.from(inflator.result)
decompressedBlocks.push(buffer)
let len = buffer.length

cpositions.push(cpos)
dpositions.push(dpos)
if (decompressedBlocks.length === 1 && chunk.minv.dataPosition) {
// this is the first chunk, trim it
decompressedBlocks[0] = decompressedBlocks[0].slice(
chunk.minv.dataPosition,
)
len = decompressedBlocks[0].length
}
const origCpos = cpos
cpos += strm.next_in
dpos += len

if (origCpos >= chunk.maxv.blockPosition) {
// this is the last chunk, trim it and stop decompressing
// note if it is the same block is minv it subtracts that already
// trimmed part of the slice length

decompressedBlocks[decompressedBlocks.length - 1] = decompressedBlocks[
decompressedBlocks.length - 1
].slice(
0,
chunk.maxv.blockPosition === chunk.minv.blockPosition
? chunk.maxv.dataPosition - chunk.minv.dataPosition + 1
: chunk.maxv.dataPosition + 1,
)

cpositions.push(cpos)
dpositions.push(dpos)
break
}
} while (strm.avail_in)

const buffer = Buffer.concat(decompressedBlocks)
return { buffer, cpositions, dpositions }
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
throw e
}
}

module.exports = {
unzip,
unzipChunk,
unzipChunkSlice,
pakoUnzip: unzip,
nodeUnzip: () => {
throw new Error('nodeUnzip not implemented.')
},
}
150 changes: 2 additions & 148 deletions src/unzip.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,9 @@
/* eslint-disable consistent-return */
const zlib = require('zlib')
const { promisify } = require('es6-promisify')
const { pakoUnzip, unzipChunk, unzipChunkSlice } = require('./unzip-pako')

const gunzip = promisify(zlib.gunzip)

const { Z_SYNC_FLUSH, Inflate } = require('pako')

// browserify-zlib, which is the zlib shim used by default in webpacked code,
// does not properly uncompress bgzf chunks that contain more than
// one bgzf block, so export an unzip function that uses pako directly
// if we are running in a browser.
async function pakoUnzip(inputData) {
try {
let strm
let pos = 0
let i = 0
const chunks = []
let inflator
do {
const remainingInput = inputData.slice(pos)
inflator = new Inflate()
;({ strm } = inflator)
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

pos += strm.next_in
chunks[i] = Buffer.from(inflator.result)
i += 1
} while (strm.avail_in)

const result = Buffer.concat(chunks)
return result
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
}
}

// similar to pakounzip, except it does extra counting
// to return the positions of compressed and decompressed
// data offsets
async function unzipChunk(inputData) {
try {
let strm
let cpos = 0
let dpos = 0
const blocks = []
const cpositions = []
const dpositions = []
do {
const remainingInput = inputData.slice(cpos)
const inflator = new Inflate()
// @ts-ignore
;({ strm } = inflator)
// @ts-ignore
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

// @ts-ignore
const buffer = Buffer.from(inflator.result)
blocks.push(buffer)

cpositions.push(cpos)
dpositions.push(dpos)

cpos += strm.next_in
dpos += buffer.length
} while (strm.avail_in)

const buffer = Buffer.concat(blocks)
return { buffer, cpositions, dpositions }
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
}
}

// similar to unzipChunk above but slices (0,minv.dataPosition) and (maxv.dataPosition,end) off
async function unzipChunkSlice(inputData, chunk) {
try {
let strm
let cpos = chunk.minv.blockPosition
let dpos = chunk.minv.dataPosition
const decompressedBlocks = []
const cpositions = []
const dpositions = []
do {
const remainingInput = inputData.slice(cpos - chunk.minv.blockPosition)
const inflator = new Inflate()
// @ts-ignore
;({ strm } = inflator)
// @ts-ignore
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) throw new Error(inflator.msg)

// @ts-ignore
const buffer = Buffer.from(inflator.result)
decompressedBlocks.push(buffer)
let len = buffer.length

cpositions.push(cpos)
dpositions.push(dpos)
if (decompressedBlocks.length === 1 && chunk.minv.dataPosition) {
// this is the first chunk, trim it
decompressedBlocks[0] = decompressedBlocks[0].slice(
chunk.minv.dataPosition,
)
len = decompressedBlocks[0].length
}
const origCpos = cpos
cpos += strm.next_in
dpos += len

if (origCpos >= chunk.maxv.blockPosition) {
// this is the last chunk, trim it and stop decompressing
// note if it is the same block is minv it subtracts that already
// trimmed part of the slice length

decompressedBlocks[decompressedBlocks.length - 1] = decompressedBlocks[
decompressedBlocks.length - 1
].slice(
0,
chunk.maxv.blockPosition === chunk.minv.blockPosition
? chunk.maxv.dataPosition - chunk.minv.dataPosition + 1
: chunk.maxv.dataPosition + 1,
)

cpositions.push(cpos)
dpositions.push(dpos)
break
}
} while (strm.avail_in)

const buffer = Buffer.concat(decompressedBlocks)
return { buffer, cpositions, dpositions }
} catch (e) {
if (e.message.match(/incorrect header check/)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
throw e
}
}

// in node, just use the native unzipping with Z_SYNC_FLUSH
function nodeUnzip(input) {
return gunzip(input, {
Expand All @@ -158,7 +12,7 @@ function nodeUnzip(input) {
}

module.exports = {
unzip: typeof __webpack_require__ === 'function' ? pakoUnzip : nodeUnzip, // eslint-disable-line
unzip: nodeUnzip,
unzipChunk,
unzipChunkSlice,
nodeUnzip,
Expand Down

0 comments on commit 983de5d

Please sign in to comment.