diff --git a/CHANGELOG.md b/CHANGELOG.md index 6520e54c3c6..f495e90b242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - Fix internal `canUseSymbol` import within `@apollo/client/utilities` to avoid breaking bundlers/builds.
[@benjamn](https://github.com/benjamn) in [#8817](https://github.com/apollographql/apollo-client/pull/8817) +- Tolerate unfreezable objects like `Uint8Array` and `Buffer` in `maybeDeepFreeze`.
+ [@geekuillaume](https://github.com/geekuillaume) and [@benjamn](https://github.com/benjamn) in [#8813](https://github.com/apollographql/apollo-client/pull/8813) + ## Apollo Client 3.4.12 ### Bug Fixes diff --git a/src/utilities/common/__tests__/maybeDeepFeeze.ts b/src/utilities/common/__tests__/maybeDeepFeeze.ts index c33cc60ca3d..07d404d5d31 100644 --- a/src/utilities/common/__tests__/maybeDeepFeeze.ts +++ b/src/utilities/common/__tests__/maybeDeepFeeze.ts @@ -14,4 +14,28 @@ describe('maybeDeepFreeze', () => { maybeDeepFreeze(foo); expect(() => (foo.bar = 1)).toThrow(); }); + + it('should avoid freezing Uint8Array', () => { + const result = maybeDeepFreeze({ array: new Uint8Array(1) }); + expect(Object.isFrozen(result)).toBe(true); + expect(Object.isFrozen(result.array)).toBe(false); + }); + + it('should avoid freezing Buffer', () => { + const result = maybeDeepFreeze({ oyez: Buffer.from("oyez") }); + expect(Object.isFrozen(result)).toBe(true); + expect(Object.isFrozen(result.oyez)).toBe(false); + }); + + it('should not freeze child properties of unfreezable objects', () => { + const result = maybeDeepFreeze({ + buffer: Object.assign(Buffer.from("oyez"), { + doNotFreeze: { please: "thanks" }, + }), + }); + expect(Object.isFrozen(result)).toBe(true); + expect(Object.isFrozen(result.buffer)).toBe(false); + expect(Object.isFrozen(result.buffer.doNotFreeze)).toBe(false); + expect(result.buffer.doNotFreeze).toEqual({ please: "thanks" }); + }); }); diff --git a/src/utilities/common/maybeDeepFreeze.ts b/src/utilities/common/maybeDeepFreeze.ts index cf9a948b84b..947e6ee31f9 100644 --- a/src/utilities/common/maybeDeepFreeze.ts +++ b/src/utilities/common/maybeDeepFreeze.ts @@ -4,8 +4,7 @@ import { isNonNullObject } from './objects'; function deepFreeze(value: any) { const workSet = new Set([value]); workSet.forEach(obj => { - if (isNonNullObject(obj)) { - if (!Object.isFrozen(obj)) Object.freeze(obj); + if (isNonNullObject(obj) && shallowFreeze(obj) === obj) { Object.getOwnPropertyNames(obj).forEach(name => { if (isNonNullObject(obj[name])) workSet.add(obj[name]); }); @@ -14,6 +13,21 @@ function deepFreeze(value: any) { return value; } +function shallowFreeze(obj: T): T | null { + if (__DEV__ && !Object.isFrozen(obj)) { + try { + Object.freeze(obj); + } catch (e) { + // Some types like Uint8Array and Node.js's Buffer cannot be frozen, but + // they all throw a TypeError when you try, so we re-throw any exceptions + // that are not TypeErrors, since that would be unexpected. + if (e instanceof TypeError) return null; + throw e; + } + } + return obj; +} + export function maybeDeepFreeze(obj: T): T { if (__DEV__) { deepFreeze(obj);