generic-filehandle2
Version:
uniform interface for accessing binary data from local files, remote HTTP resources, and browser Blob data
141 lines • 4.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
function getMessage(e) {
const r = typeof e === 'object' && e !== null && 'message' in e
? e.message
: `${e}`;
return r.replace(/\.$/, '');
}
class RemoteFile {
constructor(source, opts = {}) {
this.baseOverrides = {};
this.url = source;
const fetch = opts.fetch || globalThis.fetch.bind(globalThis);
if (opts.overrides) {
this.baseOverrides = opts.overrides;
}
this.fetchImplementation = fetch;
}
async fetch(input, init) {
let response;
try {
response = await this.fetchImplementation(input, init);
}
catch (e) {
if (`${e}`.includes('Failed to fetch')) {
// refetch to to help work around a chrome bug (discussed in
// generic-filehandle issue #72) in which the chrome cache returns a
// CORS error for content in its cache. see also
// https://github.com/GMOD/jbrowse-components/pull/1511
console.warn(`generic-filehandle: refetching ${input} to attempt to work around chrome CORS header caching bug`);
try {
response = await this.fetchImplementation(input, {
...init,
cache: 'reload',
});
}
catch (e) {
throw new Error(`${getMessage(e)} fetching ${input}`, { cause: e });
}
}
else {
throw new Error(`${getMessage(e)} fetching ${input}`, { cause: e });
}
}
return response;
}
async read(length, position, opts = {}) {
const { headers = {}, signal, overrides = {} } = opts;
if (length < Infinity) {
headers.range = `bytes=${position}-${position + length}`;
}
else if (length === Infinity && position !== 0) {
headers.range = `bytes=${position}-`;
}
const res = await this.fetch(this.url, {
...this.baseOverrides,
...overrides,
headers: {
...headers,
...overrides.headers,
...this.baseOverrides.headers,
},
method: 'GET',
redirect: 'follow',
mode: 'cors',
signal,
});
if (!res.ok) {
throw new Error(`HTTP ${res.status} fetching ${this.url}`);
}
if ((res.status === 200 && position === 0) || res.status === 206) {
const resData = await res.arrayBuffer();
// try to parse out the size of the remote file
const contentRange = res.headers.get('content-range');
const sizeMatch = /\/(\d+)$/.exec(contentRange || '');
if (sizeMatch?.[1]) {
this._stat = {
size: parseInt(sizeMatch[1], 10),
};
}
return new Uint8Array(resData.slice(0, length));
}
// eslint-disable-next-line unicorn/prefer-ternary
if (res.status === 200) {
throw new Error(`${this.url} fetch returned status 200, expected 206`);
}
else {
throw new Error(`HTTP ${res.status} fetching ${this.url}`);
}
}
async readFile(options = {}) {
let encoding;
let opts;
if (typeof options === 'string') {
encoding = options;
opts = {};
}
else {
encoding = options.encoding;
opts = options;
delete opts.encoding;
}
const { headers = {}, signal, overrides = {} } = opts;
const res = await this.fetch(this.url, {
headers,
method: 'GET',
redirect: 'follow',
mode: 'cors',
signal,
...this.baseOverrides,
...overrides,
});
if (res.status !== 200) {
throw new Error(`HTTP ${res.status} fetching ${this.url}`);
}
if (encoding === 'utf8') {
return res.text();
}
else if (encoding) {
throw new Error(`unsupported encoding: ${encoding}`);
}
else {
return new Uint8Array(await res.arrayBuffer());
}
}
async stat() {
if (!this._stat) {
await this.read(10, 0);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!this._stat) {
throw new Error(`unable to determine size of file at ${this.url}`);
}
}
return this._stat;
}
async close() {
return;
}
}
exports.default = RemoteFile;
//# sourceMappingURL=remoteFile.js.map