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);