From 7201173564cccc7182fa226bc7596a75a959404b Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:07:31 -0300 Subject: [PATCH 1/2] fix(core): load wallet & network images on React Native FetchUtil.fetchImage built its data URL with response.blob() + FileReader, but on React Native fetch(...).blob() throws "Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported" for binary responses, so wallet and network images silently failed to load. Read the bytes via response.arrayBuffer() and base64-encode them into the data URL instead (dependency-free encoder, since RN guarantees neither Buffer nor btoa). Adds unit tests for fetchImage. Co-Authored-By: Claude Opus 4.8 --- .../fix-fetch-image-blob-arraybuffer.md | 12 ++++ .../src/__tests__/utils/FetchUtil.test.ts | 57 +++++++++++++++++++ packages/core/src/utils/FetchUtil.ts | 37 ++++++++++-- 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-fetch-image-blob-arraybuffer.md diff --git a/.changeset/fix-fetch-image-blob-arraybuffer.md b/.changeset/fix-fetch-image-blob-arraybuffer.md new file mode 100644 index 000000000..7d4659396 --- /dev/null +++ b/.changeset/fix-fetch-image-blob-arraybuffer.md @@ -0,0 +1,12 @@ +--- +'@reown/appkit-core-react-native': patch +--- + +fix(core): load wallet & network images on React Native + +`FetchUtil.fetchImage` built its data URL with `response.blob()` + `FileReader`, +but on React Native `fetch(...).blob()` throws "Creating blobs from 'ArrayBuffer' +and 'ArrayBufferView' are not supported" for binary responses — so wallet and +network images silently failed to load (placeholders only). Read the bytes via +`response.arrayBuffer()` and base64-encode them into the data URL instead, using +a dependency-free encoder (RN guarantees neither `Buffer` nor `btoa`). diff --git a/packages/core/src/__tests__/utils/FetchUtil.test.ts b/packages/core/src/__tests__/utils/FetchUtil.test.ts index 0e864fb4d..b43002538 100644 --- a/packages/core/src/__tests__/utils/FetchUtil.test.ts +++ b/packages/core/src/__tests__/utils/FetchUtil.test.ts @@ -104,4 +104,61 @@ describe('FetchUtil', () => { expect(url).toBe('https://another.com/test?foo=bar&bar=baz&clientId=test-client-id'); }); }); + + describe('fetchImage', () => { + const originalFetch = global.fetch; + + afterEach(() => { + global.fetch = originalFetch; + }); + + it('should build a base64 data URL from the response ArrayBuffer', async () => { + // "foobar" -> base64 "Zm9vYmFy" + const bytes = new Uint8Array([0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]); + global.fetch = jest.fn().mockResolvedValue({ + arrayBuffer: async () => bytes.buffer, + headers: { get: () => 'image/png' } + }) as unknown as typeof fetch; + + const fetchUtil = new FetchUtil({ baseUrl }); + const result = await fetchUtil.fetchImage('/getWalletImage/1'); + + expect(result).toBe('data:image/png;base64,Zm9vYmFy'); + }); + + it('should default the content type to image/png when the header is missing', async () => { + global.fetch = jest.fn().mockResolvedValue({ + arrayBuffer: async () => new Uint8Array([0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]).buffer, + headers: { get: () => null } + }) as unknown as typeof fetch; + + const fetchUtil = new FetchUtil({ baseUrl }); + const result = await fetchUtil.fetchImage('/getWalletImage/1'); + + expect(result).toBe('data:image/png;base64,Zm9vYmFy'); + }); + + it('should not call response.blob() (RN cannot build a Blob from an ArrayBuffer)', async () => { + const blob = jest.fn(); + global.fetch = jest.fn().mockResolvedValue({ + arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer, + headers: { get: () => 'image/webp' }, + blob + }) as unknown as typeof fetch; + + const fetchUtil = new FetchUtil({ baseUrl }); + await fetchUtil.fetchImage('/getWalletImage/1'); + + expect(blob).not.toHaveBeenCalled(); + }); + + it('should return undefined when the request throws', async () => { + global.fetch = jest.fn().mockRejectedValue(new Error('network error')) as unknown as typeof fetch; + + const fetchUtil = new FetchUtil({ baseUrl }); + const result = await fetchUtil.fetchImage('/getWalletImage/1'); + + expect(result).toBeUndefined(); + }); + }); }); diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index f0b3451c2..1a69f95d1 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -80,18 +80,43 @@ export class FetchUtil { try { const url = this.createUrl({ path, params }).toString(); const response = await fetch(url, { headers }); - const blob = await response.blob(); - const reader = new FileReader(); - reader.readAsDataURL(blob); - return new Promise(resolve => { - reader.onloadend = () => resolve(reader.result as string); - }); + // React Native's `fetch(...).blob()` throws "Creating blobs from + // 'ArrayBuffer' and 'ArrayBufferView' are not supported" for binary + // responses, so Blob + FileReader can't be used to build the data URL + // (wallet/network images would silently fail to load). Read the bytes as + // an ArrayBuffer and base64-encode them into a data URL instead. + const arrayBuffer = await response.arrayBuffer(); + const contentType = response.headers.get('content-type') ?? 'image/png'; + + return `data:${contentType};base64,${FetchUtil._arrayBufferToBase64(arrayBuffer)}`; } catch { return undefined; } } + // Dependency-free base64 encoder (RN has no global `Buffer`/`btoa` guarantee). + /* eslint-disable no-bitwise */ + private static _arrayBufferToBase64(buffer: ArrayBuffer): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const bytes = new Uint8Array(buffer); + let base64 = ''; + + for (let i = 0; i < bytes.length; i += 3) { + const byte0 = bytes[i] as number; + const byte1 = i + 1 < bytes.length ? (bytes[i + 1] as number) : 0; + const byte2 = i + 2 < bytes.length ? (bytes[i + 2] as number) : 0; + + base64 += chars[byte0 >> 2]; + base64 += chars[((byte0 & 0x03) << 4) | (byte1 >> 4)]; + base64 += i + 1 < bytes.length ? chars[((byte1 & 0x0f) << 2) | (byte2 >> 6)] : '='; + base64 += i + 2 < bytes.length ? chars[byte2 & 0x3f] : '='; + } + + return base64; + } + /* eslint-enable no-bitwise */ + public createUrl({ path, params }: RequestArguments) { let fullUrl: string; From ee0c46625ea805b290b7d293f3ac141b9acfceb5 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:35:55 -0300 Subject: [PATCH 2/2] chore(core): fix prettier formatting in FetchUtil test Co-Authored-By: Claude Opus 4.8 --- packages/core/src/__tests__/utils/FetchUtil.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/__tests__/utils/FetchUtil.test.ts b/packages/core/src/__tests__/utils/FetchUtil.test.ts index b43002538..49e08b8da 100644 --- a/packages/core/src/__tests__/utils/FetchUtil.test.ts +++ b/packages/core/src/__tests__/utils/FetchUtil.test.ts @@ -153,7 +153,9 @@ describe('FetchUtil', () => { }); it('should return undefined when the request throws', async () => { - global.fetch = jest.fn().mockRejectedValue(new Error('network error')) as unknown as typeof fetch; + global.fetch = jest + .fn() + .mockRejectedValue(new Error('network error')) as unknown as typeof fetch; const fetchUtil = new FetchUtil({ baseUrl }); const result = await fetchUtil.fetchImage('/getWalletImage/1');