UNPKG

@apr144/generic-filehandle

Version:

uniform interface for accessing binary data from local files, remote HTTP resources, and browser Blob data

178 lines 8.33 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const buffer_1 = require("buffer"); const base_64_1 = require("base-64"); class RemoteFile { getBufferFromResponse(response) { return __awaiter(this, void 0, void 0, function* () { if (typeof response.buffer === 'function') { return response.buffer(); } else if (typeof response.arrayBuffer === 'function') { const resp = yield response.arrayBuffer(); return buffer_1.Buffer.from(resp); } else { throw new TypeError('invalid HTTP response object, has no buffer method, and no arrayBuffer method'); } }); } constructor(source, opts = {}) { this.auth = {}; this.baseOverrides = {}; this.url = source; this.auth = opts.auth || {}; const fetch = opts.fetch || globalThis.fetch.bind(globalThis); if (!fetch) { throw new TypeError(`no fetch function supplied, and none found in global environment`); } if (opts.overrides) { this.baseOverrides = opts.overrides; } this.fetchImplementation = fetch; } fetch(input, init) { return __awaiter(this, void 0, void 0, function* () { let response; try { init = init || {}; if (this.auth && this.auth.user && this.auth.password) { init.credentials = 'include'; init.headers = {}; init.headers.Authorization = `Basic ${(0, base_64_1.encode)(this.auth.user + ':' + this.auth.password)}`; } response = yield this.fetchImplementation(input, Object.assign({}, 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`); init = init || {}; if (this.auth && this.auth.user && this.auth.password) { init.credentials = 'include'; init.headers = {}; init.headers.Authorization = `Basic ${(0, base_64_1.encode)(this.auth.user + ':' + this.auth.password)}`; } response = yield this.fetchImplementation(input, Object.assign(Object.assign({}, init), { // headers: headers, cache: 'reload' })); } else { throw e; } } return response; }); } read(buffer, offset = 0, length, position = 0, opts = {}) { return __awaiter(this, void 0, void 0, function* () { 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 args = Object.assign(Object.assign(Object.assign({}, this.baseOverrides), overrides), { headers: Object.assign(Object.assign(Object.assign({}, headers), overrides.headers), this.baseOverrides.headers), method: 'GET', redirect: 'follow', mode: 'cors', signal }); if (this.auth && this.auth.user && this.auth.password) { headers.Authorization = `Basic ${(0, base_64_1.encode)(this.auth.user + ':' + this.auth.password)}`; args.credentials = 'include'; } const response = yield this.fetch(this.url, args); if (!response.ok) { throw new Error(`HTTP ${response.status} ${response.statusText} ${this.url}`); } if ((response.status === 200 && position === 0) || response.status === 206) { const responseData = yield this.getBufferFromResponse(response); const bytesCopied = responseData.copy(buffer, offset, 0, Math.min(length, responseData.length)); // try to parse out the size of the remote file const res = response.headers.get('content-range'); const sizeMatch = /\/(\d+)$/.exec(res || ''); if (sizeMatch && sizeMatch[1]) { this._stat = { size: parseInt(sizeMatch[1], 10) }; } return { bytesRead: bytesCopied, buffer }; } if (response.status === 200) { throw new Error('${this.url} fetch returned status 200, expected 206'); } if (response.status === 404) { return { bytesRead: 0, buffer }; } // TODO: try harder here to gather more information about what the problem is throw new Error(`HTTP ${response.status} fetching ${this.url}`); }); } readFile(options = {}) { return __awaiter(this, void 0, void 0, function* () { 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 args = Object.assign(Object.assign({ headers, method: 'GET', redirect: 'follow', mode: 'cors', signal }, this.baseOverrides), overrides); if (this.auth && this.auth.user && this.auth.password) { headers.Authorization = `Basic ${(0, base_64_1.encode)(this.auth.user + ':' + this.auth.password)}`; args.credentials = 'include'; } const response = yield this.fetch(this.url, args); if (!response) { throw new Error('generic-filehandle failed to fetch'); } if (response.status === 404) { return buffer_1.Buffer.alloc(1); } if (response.status !== 200) { throw Object.assign(new Error(`HTTP ${response.status} fetching ${this.url}`), { status: response.status, }); } if (encoding === 'utf8') { return response.text(); } if (encoding) { throw new Error(`unsupported encoding: ${encoding}`); } return this.getBufferFromResponse(response); }); } stat() { return __awaiter(this, void 0, void 0, function* () { if (!this._stat) { const buf = buffer_1.Buffer.allocUnsafe(10); yield this.read(buf, 0, 10, 0); if (!this._stat) { throw new Error(`unable to determine size of file at ${this.url}`); } } return this._stat; }); } close() { return __awaiter(this, void 0, void 0, function* () { return; }); } } exports.default = RemoteFile; //# sourceMappingURL=remoteFile.js.map