Skip to content

Commit bea86b0

Browse files
chore(encoding): Use global TextEncode when available (#4874)
1 parent 3988733 commit bea86b0

File tree

5 files changed

+72
-7
lines changed

5 files changed

+72
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
### Major Changes
1919

2020
- `Sentry.captureUserFeedback` removed, use `Sentry.captureFeedback` instead ([#4855](https://github.com/getsentry/sentry-react-native/pull/4855))
21+
- Use global `TextEncoder` (available with Hermes in React Native 0.74 or higher) to greatly improve envelope encoding performance. ([#4874](https://github.com/getsentry/sentry-react-native/pull/4874))
2122

2223
### Changes
2324

packages/core/src/js/transports/encodePolyfill.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1-
import { getMainCarrier, SDK_VERSION } from '@sentry/core';
1+
import { getSentryCarrier } from '../utils/carrier';
2+
import { RN_GLOBAL_OBJ } from '../utils/worldwide';
23
import { utf8ToBytes } from '../vendor';
34

45
export const useEncodePolyfill = (): void => {
5-
// Based on https://github.com/getsentry/sentry-javascript/blob/f0fc41f6166857cd97a695f5cc9a18caf6a0bf43/packages/core/src/carrier.ts#L49
6-
const carrier = getMainCarrier();
7-
const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {});
8-
(__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {}).encodePolyfill = encodePolyfill;
6+
const carrier = getSentryCarrier();
7+
8+
if (RN_GLOBAL_OBJ.TextEncoder) {
9+
// Hermes for RN 0.74 and later includes native TextEncoder
10+
// https://github.com/facebook/hermes/commit/8fb0496d426a8e50d00385148d5fb498a6daa312
11+
carrier.encodePolyfill = globalEncodeFactory(RN_GLOBAL_OBJ.TextEncoder);
12+
} else {
13+
carrier.encodePolyfill = encodePolyfill;
14+
}
915
};
1016

17+
/*
18+
* The default encode polyfill is available in Hermes for RN 0.74 and later.
19+
* https://github.com/facebook/hermes/commit/8fb0496d426a8e50d00385148d5fb498a6daa312
20+
*/
21+
export const globalEncodeFactory = (Encoder: EncoderClass) => (text: string) => new Encoder().encode(text);
22+
23+
type EncoderClass = Required<typeof RN_GLOBAL_OBJ>['TextEncoder'];
24+
25+
/*
26+
* Encode polyfill runs in JS and might cause performance issues when processing large payloads. (~2+ MB)
27+
*/
1128
export const encodePolyfill = (text: string): Uint8Array => {
1229
const bytes = new Uint8Array(utf8ToBytes(text));
1330
return bytes;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { getMainCarrier, SDK_VERSION as CORE_SDK_VERSION } from '@sentry/core';
2+
3+
/*
4+
* Will either get the existing sentry carrier, or create a new one.
5+
* Based on https://github.com/getsentry/sentry-javascript/blob/f0fc41f6166857cd97a695f5cc9a18caf6a0bf43/packages/core/src/carrier.ts#L49
6+
*/
7+
export const getSentryCarrier = (): SentryCarrier => {
8+
const carrier = getMainCarrier();
9+
const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {});
10+
return (__SENTRY__[CORE_SDK_VERSION] = __SENTRY__[CORE_SDK_VERSION] || {});
11+
};
12+
13+
type SentryCarrier = Required<ReturnType<typeof getMainCarrier>>['__SENTRY__'][string];

packages/core/src/js/utils/worldwide.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ export interface ReactNativeInternalGlobal extends InternalGlobal {
3636
}
3737

3838
type TextEncoder = {
39-
new (): TextEncoder;
40-
encode(input?: string): Uint8Array;
39+
new (): {
40+
encode(input?: string): Uint8Array;
41+
};
4142
};
4243

4344
/** Get's the global object for the current JavaScript runtime */
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { SDK_VERSION } from '@sentry/core';
2+
import { encodePolyfill, useEncodePolyfill } from '../../src/js/transports/encodePolyfill';
3+
import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide';
4+
5+
const OriginalTextEncoder = RN_GLOBAL_OBJ.TextEncoder;
6+
7+
const restoreTextEncoder = (): void => {
8+
RN_GLOBAL_OBJ.TextEncoder = OriginalTextEncoder;
9+
};
10+
11+
describe('useEncodePolyfill', () => {
12+
afterEach(() => {
13+
restoreTextEncoder();
14+
});
15+
16+
test('should use global encode factory if TextEncoder is available', () => {
17+
RN_GLOBAL_OBJ.TextEncoder = MockedTextEncoder;
18+
useEncodePolyfill();
19+
expect(RN_GLOBAL_OBJ.__SENTRY__?.[SDK_VERSION]?.encodePolyfill?.('')).toEqual(new Uint8Array([1, 2, 3]));
20+
});
21+
22+
test('should use encode polyfill if TextEncoder is not available', () => {
23+
RN_GLOBAL_OBJ.TextEncoder = undefined;
24+
useEncodePolyfill();
25+
expect(RN_GLOBAL_OBJ.__SENTRY__?.[SDK_VERSION]?.encodePolyfill).toBe(encodePolyfill);
26+
});
27+
});
28+
29+
class MockedTextEncoder {
30+
public encode(_text: string): Uint8Array {
31+
return new Uint8Array([1, 2, 3]);
32+
}
33+
}

0 commit comments

Comments
 (0)