diff --git a/src/common.ts b/src/common.ts index edee0efd..de944c7c 100644 --- a/src/common.ts +++ b/src/common.ts @@ -14,8 +14,16 @@ import {Agent} from 'http'; import {URL} from 'url'; -/* eslint-disable @typescript-eslint/no-explicit-any */ +import {pkg} from './util'; +/** + * Support `instanceof` operator for `GaxiosError`s in different versions of this library. + * + * @see {@link GaxiosError[Symbol.hasInstance]} + */ +export const GAXIOS_ERROR_SYMBOL = Symbol.for(`${pkg.name}-gaxios-error`); + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export class GaxiosError extends Error { /** * An Error code. @@ -34,6 +42,37 @@ export class GaxiosError extends Error { */ status?: number; + /** + * Support `instanceof` operator for `GaxiosError` across builds/duplicated files. + * + * @see {@link GAXIOS_ERROR_SYMBOL} + * @see {@link GaxiosError[Symbol.hasInstance]} + * @see {@link https://github.com/microsoft/TypeScript/issues/13965#issuecomment-278570200} + * @see {@link https://stackoverflow.com/questions/46618852/require-and-instanceof} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/@@hasInstance#reverting_to_default_instanceof_behavior} + */ + [GAXIOS_ERROR_SYMBOL] = pkg.version; + + /** + * Support `instanceof` operator for `GaxiosError` across builds/duplicated files. + * + * @see {@link GAXIOS_ERROR_SYMBOL} + * @see {@link GaxiosError[GAXIOS_ERROR_SYMBOL]} + */ + static [Symbol.hasInstance](instance: unknown) { + if ( + instance && + typeof instance === 'object' && + GAXIOS_ERROR_SYMBOL in instance && + instance[GAXIOS_ERROR_SYMBOL] === pkg.version + ) { + return true; + } + + // fallback to native + return Function.prototype[Symbol.hasInstance].call(GaxiosError, instance); + } + constructor( message: string, public config: GaxiosOptions, diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 00000000..1428c3bc --- /dev/null +++ b/src/util.ts @@ -0,0 +1,17 @@ +// Copyright 2023 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const pkg: { + name: string; + version: string; +} = require('../../package.json'); diff --git a/test/test.getch.ts b/test/test.getch.ts index 844cba04..f61eeea8 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -26,6 +26,8 @@ import { GaxiosResponse, GaxiosPromise, } from '../src'; +import {GAXIOS_ERROR_SYMBOL} from '../src/common'; +import {pkg} from '../src/util'; import qs from 'querystring'; import fs from 'fs'; import {Blob} from 'node-fetch'; @@ -119,6 +121,18 @@ describe('🚙 error handling', () => { assert(error.response, undefined); assert.equal(error.response.data, notJSON); }); + + it('should support `instanceof` for GaxiosErrors of the same version', () => { + class A extends GaxiosError {} + + const wrongVersion = {[GAXIOS_ERROR_SYMBOL]: '0.0.0'}; + const correctVersion = {[GAXIOS_ERROR_SYMBOL]: pkg.version}; + const child = new A('', {}); + + assert.equal(wrongVersion instanceof GaxiosError, false); + assert.equal(correctVersion instanceof GaxiosError, true); + assert.equal(child instanceof GaxiosError, true); + }); }); describe('🥁 configuration options', () => {