diff --git a/src/remoteFile.ts b/src/remoteFile.ts index a84584b..1dddc09 100644 --- a/src/remoteFile.ts +++ b/src/remoteFile.ts @@ -18,7 +18,7 @@ const myGlobal = export default class RemoteFile implements GenericFilehandle { private url: string private _stat?: Stats - private fetch: Fetcher + private fetchImplementation: Fetcher private baseOverrides: any = {} private async getBufferFromResponse(response: PolyfilledResponse): Promise { @@ -49,7 +49,7 @@ export default class RemoteFile implements GenericFilehandle { this.stat = localFile.stat.bind(localFile) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - this.fetch = (): void => { + this.fetchImplementation = (): void => { /* intentionally blank */ } return @@ -64,7 +64,30 @@ export default class RemoteFile implements GenericFilehandle { if (opts.overrides) { this.baseOverrides = opts.overrides } - this.fetch = fetch + this.fetchImplementation = fetch + } + + public async fetch( + input: RequestInfo, + init: RequestInit | undefined, + ): Promise { + let response + try { + response = await this.fetchImplementation(input, init) + } catch (e) { + if (e.message === '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`, + ) + response = await this.fetchImplementation(input, { ...init, cache: 'reload' }) + } else { + throw e + } + } + return response } public async read( @@ -91,6 +114,10 @@ export default class RemoteFile implements GenericFilehandle { } const response = await this.fetch(this.url, args) + if (!response.ok) { + throw new Error(`HTTP ${response.status} ${response.statusText}`) + } + if ((response.status === 200 && position === 0) || response.status === 206) { const responseData = await this.getBufferFromResponse(response) const bytesCopied = responseData.copy( @@ -132,7 +159,7 @@ export default class RemoteFile implements GenericFilehandle { delete opts.encoding } const { headers = {}, signal, overrides = {} } = opts - const response = await this.fetch(this.url, { + const args = { headers, method: 'GET', redirect: 'follow', @@ -140,7 +167,13 @@ export default class RemoteFile implements GenericFilehandle { signal, ...this.baseOverrides, ...overrides, - }) + } + const response = await this.fetch(this.url, args) + + if (!response) { + throw new Error('generic-filehandle failed to fetch') + } + if (response.status !== 200) { throw Object.assign(new Error(`HTTP ${response.status} fetching ${this.url}`), { status: response.status, diff --git a/test/remoteFile.test.ts b/test/remoteFile.test.ts index 077605c..633b840 100644 --- a/test/remoteFile.test.ts +++ b/test/remoteFile.test.ts @@ -136,7 +136,7 @@ describe('remote file tests', () => { const f = new RemoteFile('http://fakehost/test.txt') const buf = Buffer.alloc(10) const res = f.read(buf, 0, 0, 0) - await expect(res).rejects.toThrow(/fetching/) + await expect(res).rejects.toThrow(/Internal Server Error/) }) it('throws error if file missing', async () => { fetchMock.mock('http://fakehost/test.txt', 404)