diff --git a/packages/sdk/react-native/__tests__/platform/PlatformEncoding.test.ts b/packages/sdk/react-native/__tests__/platform/PlatformEncoding.test.ts new file mode 100644 index 0000000000..1b3ed3f807 --- /dev/null +++ b/packages/sdk/react-native/__tests__/platform/PlatformEncoding.test.ts @@ -0,0 +1,13 @@ +import PlatformEncoding from '../../src/platform/PlatformEncoding'; + +it('can base64 a basic ASCII string', () => { + const encoding = new PlatformEncoding(); + expect(encoding.btoa('toaster')).toEqual('dG9hc3Rlcg=='); +}); + +it('can base64 a unicode string containing multi-byte character', () => { + const encoding = new PlatformEncoding(); + expect(encoding.btoa('✇⽊❽⾵⊚▴ⶊ↺➹≈⋟⚥⤅⊈ⲏⷨ⾭Ⲗ⑲▯ⶋₐℛ⬎⿌🦄')).toEqual( + '4pyH4r2K4p294r614oqa4pa04raK4oa64p654omI4ouf4pql4qSF4oqI4rKP4reo4r6t4rKW4pGy4pav4raL4oKQ4oSb4qyO4r+M8J+mhA==', + ); +}); diff --git a/packages/sdk/react-native/src/polyfills/btoa.ts b/packages/sdk/react-native/src/polyfills/btoa.ts index d9b29bfac0..4b59523884 100644 --- a/packages/sdk/react-native/src/polyfills/btoa.ts +++ b/packages/sdk/react-native/src/polyfills/btoa.ts @@ -1,15 +1,10 @@ +/* eslint-disable no-bitwise */ import { fromByteArray } from 'base64-js'; -function convertToByteArray(s: string) { - const b = []; - for (let i = 0; i < s.length; i += 1) { - b.push(s.charCodeAt(i)); - } - return Uint8Array.from(b); -} +import toUtf8Array from './toUtf8Array'; export function btoa(s: string) { - return fromByteArray(convertToByteArray(s)); + return fromByteArray(toUtf8Array(s)); } export function base64FromByteArray(a: Uint8Array) { diff --git a/packages/sdk/react-native/src/polyfills/toUtf8Array.ts b/packages/sdk/react-native/src/polyfills/toUtf8Array.ts new file mode 100644 index 0000000000..e582754356 --- /dev/null +++ b/packages/sdk/react-native/src/polyfills/toUtf8Array.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable no-bitwise */ + +// Originally from: https://github.com/google/closure-library/blob/a1f5a029c1b32eb4793a2d920aa52abc085e1bf7/closure/goog/crypt/crypt.js + +// Once React Native versions uniformly support TextEncoder this code can be removed. + +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// 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 default function toUtf8Array(str: string): Uint8Array { + const out: number[] = []; + let p = 0; + for (let i = 0; i < str.length; i += 1) { + let c = str.charCodeAt(i); + if (c < 128) { + out[p++] = c; + } else if (c < 2048) { + out[p++] = (c >> 6) | 192; + out[p++] = (c & 63) | 128; + } else if ( + (c & 0xfc00) === 0xd800 && + i + 1 < str.length && + (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00 + ) { + // Surrogate Pair + c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff); + out[p++] = (c >> 18) | 240; + out[p++] = ((c >> 12) & 63) | 128; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } else { + out[p++] = (c >> 12) | 224; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } + } + return Uint8Array.from(out); +}