Skip to content

Commit

Permalink
feat: process rpc responses only in relying party
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker committed Aug 6, 2024
1 parent 3a9e3f8 commit 579fe1f
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/types/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,5 @@ export const inferRpcResponse = <T extends z.ZodTypeAny>(
({result, error}) => result !== undefined || error !== undefined,
'Either result or error should be provided.'
);

export const RpcResponseWithResultOrError = inferRpcResponse(z.any());
55 changes: 44 additions & 11 deletions src/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as walletHandlers from './handlers/wallet.handlers';
import {JSON_RPC_VERSION_2} from './types/rpc';
import {ICRC29_STATUS} from './types/icrc';
import {JSON_RPC_VERSION_2, RpcResponseWithResultOrError} from './types/rpc';
import {WALLET_WINDOW_CENTER, WALLET_WINDOW_TOP_RIGHT, windowFeatures} from './utils/window.utils';
import {Wallet, type WalletParameters} from './wallet';

Expand Down Expand Up @@ -89,22 +90,22 @@ describe('Wallet', () => {
}
];

const messageEventReady = new MessageEvent('message', {
origin: mockParameters.url,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: '123',
result: 'ready'
}
});

it.each(options)('$title', async ({params, expectedOptions}) => {
const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');

const promise = Wallet.connect(params);

const messageEvent = new MessageEvent('message', {
origin: mockParameters.url,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: '123',
result: 'ready'
}
});

window.dispatchEvent(messageEvent);
window.dispatchEvent(messageEventReady);

const wallet = await promise;

Expand All @@ -120,6 +121,38 @@ describe('Wallet', () => {
expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
});

it.only('should not process message which are not RpcResponse', async () => {
const safeParseSpy = vi.spyOn(RpcResponseWithResultOrError, 'safeParse');

const promise = Wallet.connect(mockParameters);

const messageEventNotRpc = new MessageEvent('message', {
data: 'test',
origin: mockParameters.url
});

window.dispatchEvent(messageEventNotRpc);

window.dispatchEvent(messageEventReady);

const wallet = await promise;

expect(wallet).toBeInstanceOf(Wallet);

// Two responses as triggered above
expect(safeParseSpy).toHaveBeenCalledWith(messageEventNotRpc.data);
expect(safeParseSpy).toHaveBeenCalledWith(messageEventReady.data);

// We are mocking the popup with the window, therefore popup.postMessage({method: ICRC29_STATUS}) results in an additional message detected by window.addEventListener
expect(safeParseSpy).toHaveBeenCalledWith(
expect.objectContaining({
method: ICRC29_STATUS
})
);

expect(safeParseSpy).toHaveBeenCalledTimes(3);
});
});
});

Expand Down
8 changes: 8 additions & 0 deletions src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {nanoid} from 'nanoid';
import {WALLET_CONNECT_DEFAULT_TIMEOUT_IN_SECONDS} from './constants/wallet.constants';
import {retryRequestStatus} from './handlers/wallet.handlers';
import {IcrcReadyResponse} from './types/icrc-responses';
import {RpcResponseWithResultOrError} from './types/rpc';
import type {ReadyOrError} from './utils/timeout.utils';
import {
WALLET_WINDOW_TOP_RIGHT,
Expand Down Expand Up @@ -67,6 +68,13 @@ export class Wallet {
let response: Wallet | MessageError | undefined;

const onMessage = ({origin, data: msgData}: MessageEvent): void => {
const {success} = RpcResponseWithResultOrError.safeParse(msgData);

if (!success) {
// We are only interested in JSON-RPC messages, so we are ignoring any other messages emitted at the window level, as the consumer might be using other events.
return;
}

// In our test suite, origin is set to empty string when the message originate from the same window - i.e. when retryRequestStatus are emitted.// In our test suite, the origin is set to an empty string when the message originates from the same window. This occurs when `retryRequestStatus` events are emitted to `*`.
if (notEmptyString(origin) && origin !== url) {
response = new MessageError(
Expand Down

0 comments on commit 579fe1f

Please sign in to comment.