Skip to content

Commit cec343a

Browse files
perf: Encode messages in mobile as byte arrays (#3077)
Base64 encoding/decoding seems to be a significant overhead for the boot message on mobile. Encoding the messages as bytes instead have proven to be more performant, but still has overhead.
1 parent 81db94d commit cec343a

File tree

5 files changed

+19
-21
lines changed

5 files changed

+19
-21
lines changed

packages/snaps-controllers/src/services/webview/WebViewMessageStream.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ describe('WebViewMessageStream', () => {
2222
expect(await responsePromise).toBe(555);
2323

2424
expect(mockWebViewA.injectJavaScript).toHaveBeenCalledWith(
25-
`window.postMessage('eyJ0YXJnZXQiOiJiIiwiZGF0YSI6MTExfQ==')`,
25+
`window.postMessage([123,34,116,97,114,103,101,116,34,58,34,98,34,44,34,100,97,116,97,34,58,34,83,89,78,34,125])`,
2626
);
2727

2828
// Inject { target: "foo", data: 111 }
2929
mockWebViewA.injectJavaScript(
30-
`window.postMessage('eyJ0YXJnZXQiOiJmb28iLCJkYXRhIjoxMTF9')`,
30+
`window.postMessage([123,34,116,97,114,103,101,116,34,58,34,102,111,111,34,44,34,100,97,116,97,34,58,49,49,49,125])`,
3131
);
3232

3333
const listener = jest.fn();

packages/snaps-controllers/src/services/webview/WebViewMessageStream.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { PostMessageEvent } from '@metamask/post-message-stream';
22
import { BasePostMessageStream } from '@metamask/post-message-stream';
33
import { isValidStreamMessage } from '@metamask/post-message-stream/dist/utils';
44
import { logError } from '@metamask/snaps-utils';
5-
import { assert, bytesToBase64, stringToBytes } from '@metamask/utils';
5+
import { assert, stringToBytes } from '@metamask/utils';
66

77
export type WebViewInterface = {
88
injectJavaScript(js: string): void;
@@ -65,12 +65,11 @@ export class WebViewMessageStream extends BasePostMessageStream {
6565
data,
6666
});
6767

68-
// To prevent XSS, we base64 encode the message before injecting it.
69-
// This adds significant performance overhead.
70-
// TODO: Should we use mobile native base64 here?
71-
const bytes = stringToBytes(json);
72-
const base64 = bytesToBase64(bytes);
73-
this.#webView.injectJavaScript(`window.postMessage('${base64}')`);
68+
// To prevent XSS, we encode the message before injecting it.
69+
// This adds significant performance overhead for larger messages.
70+
const bytes = new Uint8Array(stringToBytes(json));
71+
72+
this.#webView.injectJavaScript(`window.postMessage([${bytes.toString()}])`);
7473
}
7574

7675
private _onMessage(event: PostMessageEvent): void {

packages/snaps-controllers/src/test-utils/webview.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { base64ToBytes, bytesToString } from '@metamask/utils';
1+
import { bytesToString } from '@metamask/utils';
22

33
import { WebViewMessageStream } from '../services/webview/WebViewMessageStream';
44

55
/**
6-
* Parses the injected JS and returns the decoded base64 payload.
6+
* Parses the injected JS and returns the JSON payload.
77
*
88
* @param js - The injected JS.
9-
* @returns The parsed base64 payload as a string.
9+
* @returns The decoded JSON as a string.
1010
*/
1111
export function parseInjectedJS(js: string) {
12-
const base64 = js.slice(20, -2);
13-
const bytes = base64ToBytes(base64);
12+
const byteString = js.slice(19, -1);
13+
const bytes = new Uint8Array(JSON.parse(byteString));
1414
return bytesToString(bytes);
1515
}
1616

packages/snaps-execution-environments/src/webview/WebViewExecutorStream.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { sleep } from '@metamask/snaps-utils/test-utils';
2-
import { bytesToBase64, stringToBytes } from '@metamask/utils';
2+
import { stringToBytes } from '@metamask/utils';
33

44
import { WebViewExecutorStream } from './WebViewExecutorStream';
55

@@ -8,9 +8,8 @@ describe('WebViewExecutorStream', () => {
88
const addEventListener = jest.fn();
99
const postMessage = jest.fn().mockImplementation((message) => {
1010
const bytes = stringToBytes(message);
11-
const base64 = bytesToBase64(bytes);
1211
addEventListener.mock.calls.forEach(([_type, listener]) => {
13-
setTimeout(() => listener({ data: base64 }));
12+
setTimeout(() => listener({ data: Array.from(bytes) }));
1413
});
1514
});
1615
const mockWindow = {

packages/snaps-execution-environments/src/webview/WebViewExecutorStream.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { PostMessageEvent } from '@metamask/post-message-stream';
22
import { BasePostMessageStream } from '@metamask/post-message-stream';
33
import { isValidStreamMessage } from '@metamask/post-message-stream/dist/utils';
4-
import { base64ToBytes, bytesToString } from '@metamask/utils';
4+
import { bytesToString } from '@metamask/utils';
55

66
type WebViewExecutorStreamArgs = {
77
name: string;
@@ -62,12 +62,12 @@ export class WebViewExecutorStream extends BasePostMessageStream {
6262
}
6363

6464
private _onMessage(event: PostMessageEvent): void {
65-
if (typeof event.data !== 'string') {
65+
if (!Array.isArray(event.data)) {
6666
return;
6767
}
6868

69-
const bytes = base64ToBytes(event.data);
70-
const message = JSON.parse(bytesToString(bytes));
69+
const json = bytesToString(new Uint8Array(event.data as number[]));
70+
const message = JSON.parse(json);
7171

7272
// Notice that we don't check targetWindow or targetOrigin here.
7373
// This doesn't seem possible to do in RN.

0 commit comments

Comments
 (0)