From 3e48da80e41929658582ba4a62c412c7cc262b2a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 28 Aug 2026 14:10:14 +0100 Subject: [PATCH] polyfill structuredclone on legacy browsers Fixes APP-T4SK --- package.json | 2 ++ pnpm-lock.yaml | 11 +++++++++++ src/platform/polyfills.web.test.ts | 29 +++++++++++++++++++++++++++++ src/platform/polyfills.web.ts | 12 ++++++++++-- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/platform/polyfills.web.test.ts diff --git a/package.json b/package.json index 1ac7f45e11..5c82353490 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "@types/invariant": "^2.2.37", "@types/lodash.throttle": "^4.1.9", "@types/node": "^24.12.2", + "@ungap/structured-clone": "1.3.1", "array.prototype.findlast": "^1.2.3", "babel-plugin-transform-remove-console": "^6.9.4", "bcp-47": "^2.1.0", @@ -284,6 +285,7 @@ "@types/psl": "1.1.1", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", + "@types/ungap__structured-clone": "1.2.0", "@typescript/native": "npm:typescript@^7.0.2", "babel-jest": "^29.7.0", "babel-plugin-module-resolver": "^5.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9af9df2865..786cb53d44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -410,6 +410,9 @@ importers: '@types/node': specifier: ^24.12.2 version: 24.12.4 + '@ungap/structured-clone': + specifier: 1.3.1 + version: 1.3.1 array.prototype.findlast: specifier: ^1.2.3 version: 1.2.5 @@ -792,6 +795,9 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.4(@types/react@19.2.18) + '@types/ungap__structured-clone': + specifier: 1.2.0 + version: 1.2.0 '@typescript/native': specifier: npm:typescript@^7.0.2 version: typescript@7.0.2 @@ -3945,6 +3951,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/ungap__structured-clone@1.2.0': + resolution: {integrity: sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -13426,6 +13435,8 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/ungap__structured-clone@1.2.0': {} + '@types/use-sync-external-store@0.0.6': {} '@types/ws@8.18.1': diff --git a/src/platform/polyfills.web.test.ts b/src/platform/polyfills.web.test.ts new file mode 100644 index 0000000000..622c8abd20 --- /dev/null +++ b/src/platform/polyfills.web.test.ts @@ -0,0 +1,29 @@ +import {RichText} from '@bsky/sdk/richtext' + +describe('web polyfills', () => { + const nativeStructuredClone = globalThis.structuredClone + + afterEach(() => { + globalThis.structuredClone = nativeStructuredClone + }) + + it('supports rich text when structuredClone is unavailable', async () => { + Reflect.deleteProperty(globalThis, 'structuredClone') + + let structuredCloneReady: Promise | undefined + jest.isolateModules(() => { + const polyfills = + require('./polyfills.web') as typeof import('./polyfills.web') + structuredCloneReady = polyfills.structuredCloneReady + }) + await structuredCloneReady + + const richText = new RichText( + {text: 'hello\n\n\nworld'}, + {cleanNewlines: true}, + ) + + expect(richText.text).toBe('hello\n\nworld') + expect(richText.clone()).toEqual(richText) + }) +}) diff --git a/src/platform/polyfills.web.ts b/src/platform/polyfills.web.ts index dce9b284cc..79ca016d03 100644 --- a/src/platform/polyfills.web.ts +++ b/src/platform/polyfills.web.ts @@ -1,6 +1,16 @@ import 'array.prototype.findlast/auto' import 'setimmediate' +export const structuredCloneReady: Promise = + typeof globalThis.structuredClone === 'function' + ? Promise.resolve() + : import('@ungap/structured-clone').then(({default: structuredClone}) => { + if (typeof globalThis.structuredClone !== 'function') { + globalThis.structuredClone = + structuredClone as typeof globalThis.structuredClone + } + }) + if (process.env.NODE_ENV !== 'production') { // In development, react-native-web's tries to validate that // text is wrapped into . It doesn't catch all cases but is useful. @@ -30,5 +40,3 @@ if (process.env.NODE_ENV !== 'production') { } } } - -export {}