diff --git a/packages/starknet-snap/src/index.test.tsx b/packages/starknet-snap/src/index.test.tsx index 2426ddf5..236eab98 100644 --- a/packages/starknet-snap/src/index.test.tsx +++ b/packages/starknet-snap/src/index.test.tsx @@ -6,6 +6,12 @@ import { HomePageController } from './on-home-page'; import * as keyPairUtils from './utils/keyPair'; jest.mock('./utils/logger'); +jest.mock('./utils/snap'); + +jest.mock('./utils', () => ({ + ...jest.requireActual('./utils'), + updateRequiredMetaMaskComponent: jest.fn(), +})); describe('onRpcRequest', () => { const createMockSpy = () => { @@ -42,6 +48,7 @@ describe('onRpcRequest', () => { }); it('throws `MethodNotFoundError` if the request method not found', async () => { + createMockSpy(); await expect( onRpcRequest({ ...createMockRequest(), @@ -53,6 +60,19 @@ describe('onRpcRequest', () => { ).rejects.toThrow(MethodNotFoundError); }); + it('requests gets executed if MetaMask does not needs update', async () => { + createMockSpy(); + expect( + await onRpcRequest({ + ...createMockRequest(), + request: { + ...createMockRequest().request, + method: 'ping', + }, + }), + ).toBe('pong'); + }); + it('throws `SnapError` if the error is an instance of SnapError', async () => { const { createAccountSpy } = createMockSpy(); createAccountSpy.mockRejectedValue(new SnapError('error')); @@ -69,7 +89,7 @@ describe('onRpcRequest', () => { }); describe('onHomePage', () => { - it('executes homePageController', async () => { + it('executes homePageController normally if jsxSupport is not required', async () => { const executeSpy = jest.spyOn(HomePageController.prototype, 'execute'); executeSpy.mockResolvedValue({ content: text('test') }); diff --git a/packages/starknet-snap/src/index.tsx b/packages/starknet-snap/src/index.tsx index 37e264df..26596246 100644 --- a/packages/starknet-snap/src/index.tsx +++ b/packages/starknet-snap/src/index.tsx @@ -61,6 +61,7 @@ import type { import type { SnapState } from './types/snapState'; import { upgradeAccContract } from './upgradeAccContract'; import { + ensureJsxSupport, getDappUrl, getStateData, isSnapRpcError, @@ -302,40 +303,27 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }; export const onInstall: OnInstallHandler = async () => { - await snap.request({ - method: 'snap_dialog', - params: { - type: 'alert', - content: ( - - Your MetaMask wallet is now compatible with Starknet! - - To manage your Starknet account and send and receive funds, visit - the companion dapp for Starknet. - - - ), - }, - }); + await ensureJsxSupport( + + Your MetaMask wallet is now compatible with Starknet! + + To manage your Starknet account and send and receive funds, visit the{' '} + companion dapp for Starknet. + + , + ); }; export const onUpdate: OnUpdateHandler = async () => { - await snap.request({ - method: 'snap_dialog', - params: { - type: 'alert', - content: ( - - Your Starknet Snap is now up-to-date ! - - As usual, to manage your Starknet account and send and receive - funds, visit the{' '} - companion dapp for Starknet. - - - ), - }, - }); + await ensureJsxSupport( + + Your Starknet Snap is now up-to-date ! + + As usual, to manage your Starknet account and send and receive funds, + visit the companion dapp for Starknet. + + , + ); }; export const onHomePage: OnHomePageHandler = async () => { diff --git a/packages/starknet-snap/src/state/state-manager.ts b/packages/starknet-snap/src/state/state-manager.ts index 81b7a997..30bb0e75 100644 --- a/packages/starknet-snap/src/state/state-manager.ts +++ b/packages/starknet-snap/src/state/state-manager.ts @@ -14,6 +14,7 @@ export abstract class StateManager extends SnapStateManager { erc20Tokens: [], networks: [], transactions: [], + requireMMUpgrade: undefined, transactionRequests: [], }; } diff --git a/packages/starknet-snap/src/types/snapState.ts b/packages/starknet-snap/src/types/snapState.ts index ceea001a..341280db 100644 --- a/packages/starknet-snap/src/types/snapState.ts +++ b/packages/starknet-snap/src/types/snapState.ts @@ -7,6 +7,7 @@ export type SnapState = { networks: Network[]; transactions: Transaction[]; currentNetwork?: Network; + requireMMUpgrade?: boolean; transactionRequests?: TransactionRequest[]; }; diff --git a/packages/starknet-snap/src/utils/snap-ui.ts b/packages/starknet-snap/src/utils/snap-ui.ts index 0aaafacb..eb655ac0 100644 --- a/packages/starknet-snap/src/utils/snap-ui.ts +++ b/packages/starknet-snap/src/utils/snap-ui.ts @@ -1,9 +1,50 @@ -import { divider, heading, row, text } from '@metamask/snaps-sdk'; +import type { Component } from '@metamask/snaps-sdk'; +import { divider, heading, panel, row, text } from '@metamask/snaps-sdk'; import { getExplorerUrl } from './explorer'; import { toJson } from './serializer'; import { shortenAddress } from './string'; +export const updateRequiredMetaMaskComponent = () => { + return panel([ + text( + 'You need to update your MetaMask to latest version to use this snap.', + ), + ]); +}; + +/** + * Ensures that JSX support is available in the MetaMask environment by attempting to render a component within a snap dialog. + * If MetaMask does not support JSX, an alert message is shown prompting the user to update MetaMask. + * + * @param component - The JSX component to display in the snap dialog. + * + * The function performs the following steps: + * 1. Tries to render the provided component using a `snap_dialog` method. + * 2. On success, it updates the `requireMMUpgrade` flag in the snap's state to `false`, indicating that JSX is supported. + * 3. If an error occurs (likely due to outdated MetaMask), it displays an alert dialog prompting the user to update MetaMask. + */ +export const ensureJsxSupport = async (component: Component): Promise => { + try { + // Try rendering the JSX component to test compatibility + await snap.request({ + method: 'snap_dialog', + params: { + type: 'alert', + content: component, + }, + }); + } catch { + await snap.request({ + method: 'snap_dialog', + params: { + type: 'alert', + content: updateRequiredMetaMaskComponent(), + }, + }); + } +}; + /** * Build a row component. *