Skip to content

Commit

Permalink
Merge branch 'main' into fix-localstorage-issue-on-serverside
Browse files Browse the repository at this point in the history
  • Loading branch information
chakra-guy authored Jan 14, 2025
2 parents b02ed70 + 801c9ef commit 1edf4b5
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 11 deletions.
4 changes: 4 additions & 0 deletions packages/sdk-install-modal-web/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { TrackingEvents } from "./components/misc/tracking-events";
export { TrackingEvents } from "./components/misc/tracking-events";
export namespace Components {
interface MmInstallModal {
/**
Expand Down Expand Up @@ -47,6 +49,7 @@ declare global {
interface HTMLMmInstallModalElementEventMap {
"close": any;
"startDesktopOnboarding": any;
"trackAnalytics": { event: TrackingEvents, params?: Record<string, unknown> };
}
interface HTMLMmInstallModalElement extends Components.MmInstallModal, HTMLStencilElement {
addEventListener<K extends keyof HTMLMmInstallModalElementEventMap>(type: K, listener: (this: HTMLMmInstallModalElement, ev: MmInstallModalCustomEvent<HTMLMmInstallModalElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
Expand Down Expand Up @@ -113,6 +116,7 @@ declare namespace LocalJSX {
"link"?: string;
"onClose"?: (event: MmInstallModalCustomEvent<any>) => void;
"onStartDesktopOnboarding"?: (event: MmInstallModalCustomEvent<any>) => void;
"onTrackAnalytics"?: (event: MmInstallModalCustomEvent<{ event: TrackingEvents, params?: Record<string, unknown> }>) => void;
"preferDesktop"?: boolean;
"sdkVersion"?: string;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum TrackingEvents {
SDK_MODAL_VIEWED = 'sdk_modal_viewed',
SDK_MODAL_BUTTON_CLICKED = 'sdk_modal_button_clicked',
SDK_MODAL_TOGGLE_CHANGED = 'sdk_modal_toggle_changed',
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import CloseButton from '../misc/CloseButton';
import Logo from '../misc/Logo';
import encodeQR from '@paulmillr/qr';
import { SimpleI18n } from '../misc/simple-i18n';
import { TrackingEvents } from '../misc/tracking-events';

@Component({
tag: 'mm-install-modal',
styleUrl: '../style.css',
Expand All @@ -31,6 +33,8 @@ export class InstallModal {

@Event() startDesktopOnboarding: EventEmitter;

@Event() trackAnalytics: EventEmitter<{ event: TrackingEvents, params?: Record<string, unknown> }>;

@State() tab: number = 1;

@State() isDefaultTab: boolean = true;
Expand All @@ -49,6 +53,16 @@ export class InstallModal {
this.i18nInstance = new SimpleI18n();
}

componentDidLoad() {
this.trackAnalytics.emit({
event: TrackingEvents.SDK_MODAL_VIEWED,
params: {
extensionInstalled: false,
tab: this.tab === 1 ? 'desktop' : 'mobile',
},
});
}

async connectedCallback() {
await this.i18nInstance.init({
fallbackLng: 'en'
Expand All @@ -70,11 +84,27 @@ export class InstallModal {
}

onStartDesktopOnboardingHandler() {
this.trackAnalytics.emit({
event: TrackingEvents.SDK_MODAL_BUTTON_CLICKED,
params: {
button_type: 'install_extension',
tab: 'desktop',
},
});
this.startDesktopOnboarding.emit();
}

setTab(newTab: number) {
this.tab = newTab
setTab(newTab: number, isUserAction: boolean = false) {
if (isUserAction) {
this.trackAnalytics.emit({
event: TrackingEvents.SDK_MODAL_TOGGLE_CHANGED,
params: {
toggle: this.tab === 1 ? 'desktop_to_mobile' : 'mobile_to_desktop',
},
});
}

this.tab = newTab;
this.isDefaultTab = false;
}

Expand All @@ -84,8 +114,7 @@ export class InstallModal {
}

const t = (key: string) => this.i18nInstance.t(key);

const currentTab = this.isDefaultTab ? this.preferDesktop ? 1 : 2 : this.tab
const currentTab = this.isDefaultTab ? this.preferDesktop ? 1 : 2 : this.tab;

const svgElement = encodeQR(this.link, "svg", {
ecc: "medium",
Expand All @@ -110,13 +139,13 @@ export class InstallModal {
<div class='tabcontainer'>
<div class='flexContainer'>
<div
onClick={() => this.setTab(1)}
onClick={() => this.setTab(1, true)}
class={`tab flexItem ${currentTab === 1 ? 'tabactive': ''}`}
>
{t('DESKTOP')}
</div>
<div
onClick={() => this.setTab(2)}
onClick={() => this.setTab(2, true)}
class={`tab flexItem ${currentTab === 2 ? 'tabactive': ''}`}
>
{t('MOBILE')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { renderHook } from '@testing-library/react-hooks';
import { useHandleTerminateEvent } from './useHandleTerminateEvent';
import { EventHandlerProps } from '../MetaMaskProvider';
import * as loggerModule from '../utils/logger';

describe('useHandleTerminateEvent', () => {
const spyLogger = jest.spyOn(loggerModule, 'logger');

const eventHandlerProps = {
setConnecting: jest.fn(),
setConnected: jest.fn(),
setError: jest.fn(),
debug: true,
} as unknown as EventHandlerProps;

beforeEach(() => {
jest.clearAllMocks();

eventHandlerProps.setConnecting = jest.fn();
eventHandlerProps.setConnected = jest.fn();
eventHandlerProps.setError = jest.fn();
});

it('should handle the terminate event correctly', () => {
const mockReason = { message: 'Terminated due to xyz', code: -32000 };

const { result } = renderHook(() =>
useHandleTerminateEvent(eventHandlerProps),
);
result.current(mockReason);

expect(spyLogger).toHaveBeenCalledWith(
"[MetaMaskProvider: useHandleTerminateEvent()] on 'terminate' event.",
mockReason,
);

expect(eventHandlerProps.setConnecting).toHaveBeenCalledWith(false);
expect(eventHandlerProps.setConnected).toHaveBeenCalledWith(false);
expect(eventHandlerProps.setError).toHaveBeenCalledWith(mockReason);
});
});
25 changes: 25 additions & 0 deletions packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EthereumRpcError } from 'eth-rpc-errors';
import { useCallback } from 'react';
import { EventHandlerProps } from '../MetaMaskProvider';
import { logger } from '../utils/logger';

export const useHandleTerminateEvent = ({
debug,
setConnecting,
setConnected,
setError,
}: EventHandlerProps) => {
return useCallback(
(reason: unknown) => {
logger(
`[MetaMaskProvider: useHandleTerminateEvent()] on 'terminate' event.`,
reason,
);

setConnecting(false);
setConnected(false);
setError(reason as EthereumRpcError<unknown>);
},
[debug, setConnecting, setConnected, setError],
);
};
6 changes: 4 additions & 2 deletions packages/sdk-react/src/MetaMaskProvider.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('MetaMaskProvider Component', () => {
});

expect(mockSdkOn).toHaveBeenCalledTimes(2);
expect(mockProviderOn).toHaveBeenCalledTimes(6);
expect(mockProviderOn).toHaveBeenCalledTimes(7);

expect(mockSdkOn.mock.calls).toEqual([
['service_status', expect.any(Function)],
Expand All @@ -126,6 +126,7 @@ describe('MetaMaskProvider Component', () => {

expect(mockProviderOn.mock.calls).toEqual([
['_initialized', expect.any(Function)],
['terminate', expect.any(Function)],
['connecting', expect.any(Function)],
['connect', expect.any(Function)],
['disconnect', expect.any(Function)],
Expand All @@ -144,7 +145,7 @@ describe('MetaMaskProvider Component', () => {
cleanup();

expect(mockSdkRemoveListener).toHaveBeenCalledTimes(2);
expect(mockProviderRemoveListener).toHaveBeenCalledTimes(6);
expect(mockProviderRemoveListener).toHaveBeenCalledTimes(7);

expect(mockSdkRemoveListener.mock.calls).toEqual([
['service_status', expect.any(Function)],
Expand All @@ -156,6 +157,7 @@ describe('MetaMaskProvider Component', () => {
['connecting', expect.any(Function)],
['connect', expect.any(Function)],
['disconnect', expect.any(Function)],
['terminate', expect.any(Function)],
['accountsChanged', expect.any(Function)],
['chainChanged', expect.any(Function)],
]);
Expand Down
12 changes: 11 additions & 1 deletion packages/sdk-react/src/MetaMaskProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useHandleInitializedEvent } from './EventsHandlers/useHandleInitialized
import { useHandleOnConnectingEvent } from './EventsHandlers/useHandleOnConnectingEvent';
import { useHandleProviderEvent } from './EventsHandlers/useHandleProviderEvent';
import { useHandleSDKStatusEvent } from './EventsHandlers/useHandleSDKStatusEvent';
import { useHandleTerminateEvent } from './EventsHandlers/useHandleTerminateEvent';
import { logger } from './utils/logger';

export interface EventHandlerProps {
Expand Down Expand Up @@ -130,6 +131,8 @@ const MetaMaskProviderClient = ({
const onConnect = useHandleConnectEvent(eventHandlerProps);

const onDisconnect = useHandleDisconnectEvent(eventHandlerProps);

const onTerminate = useHandleTerminateEvent(eventHandlerProps);

const onAccountsChanged = useHandleAccountsChangedEvent(eventHandlerProps);

Expand Down Expand Up @@ -262,12 +265,18 @@ const MetaMaskProviderClient = ({
console.warn(`[MetaMaskProviderClient] activeProvider is undefined.`);
return;
}
setConnected(activeProvider.isConnected());

const isConnected = sdk.isExtensionActive()
? !!activeProvider.getSelectedAddress()
: activeProvider.isConnected();

setConnected(isConnected);
setAccount(activeProvider.getSelectedAddress() || undefined);
setProvider(activeProvider);
setChainId(activeProvider.getChainId() || undefined);

activeProvider.on('_initialized', onInitialized);
activeProvider.on('terminate', onTerminate);
activeProvider.on('connecting', onConnecting);
activeProvider.on('connect', onConnect);
activeProvider.on('disconnect', onDisconnect);
Expand Down Expand Up @@ -296,6 +305,7 @@ const MetaMaskProviderClient = ({
activeProvider.removeListener('connecting', onConnecting);
activeProvider.removeListener('connect', onConnect);
activeProvider.removeListener('disconnect', onDisconnect);
activeProvider.removeListener('terminate', onTerminate);
activeProvider.removeListener('accountsChanged', onAccountsChanged);
activeProvider.removeListener('chainChanged', onChainChanged);
sdk.removeListener(EventType.SERVICE_STATUS, onSDKStatusEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('terminate', () => {
it('should not switch providers if extensionOnly option is true', async () => {
instance.options.extensionOnly = true;
await terminate(instance);
expect(mockEmit).not.toHaveBeenCalled();
expect(mockEmit).toHaveBeenCalledTimes(1);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export async function terminate(instance: MetaMaskSDK) {
}

if (instance.options.extensionOnly) {
instance.emit(
MetaMaskSDKEvent.ProviderUpdate,
PROVIDER_UPDATE_TYPE.TERMINATE,
);

logger(
`[MetaMaskSDK: terminate()] extensionOnly --- prevent switching providers`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('showInstallModal', () => {
terminate: expect.any(Function),
debug: state.developerMode,
connectWithExtension: expect.any(Function),
onAnalyticsEvent: expect.any(Function),
});
expect(mockModalsInstall).toHaveBeenCalledTimes(1);
expect(mockInstallModalMount).toHaveBeenCalledWith(link);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TrackingEvents } from '@metamask/sdk-communication-layer';
import { logger } from '../../../utils/logger';
import {
RemoteConnectionProps,
Expand Down Expand Up @@ -34,6 +35,22 @@ export function showInstallModal(
options.connectWithExtensionProvider?.();
return false;
},
onAnalyticsEvent: ({
event,
params,
}: {
event: TrackingEvents;
params?: Record<string, any>;
}) => {
const extended = {
...params,
sdkVersion: options.sdk.getVersion(),
dappId: options.dappMetadata?.name,
source: options._source,
url: options.dappMetadata?.url,
};
state.analytics?.send({ event, params: extended });
},
});
state.installModal?.mount?.(link);
}
10 changes: 9 additions & 1 deletion packages/sdk/src/services/RemoteConnection/RemoteConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
KeyInfo,
RemoteCommunication,
StorageManagerProps,
TrackingEvents,
} from '@metamask/sdk-communication-layer';
import { MetaMaskInstaller } from '../../Platform/MetaMaskInstaller';
import { PlatformManager } from '../../Platform/PlatfformManager';
Expand Down Expand Up @@ -55,13 +56,20 @@ export interface RemoteConnectionProps {
*/
modals: {
onPendingModalDisconnect?: () => void;
install?: (params: {
install?: (args: {
link: string;
debug?: boolean;
preferDesktop?: boolean;
installer: MetaMaskInstaller;
terminate?: () => void;
connectWithExtension?: () => void;
onAnalyticsEvent: ({
event,
params,
}: {
event: TrackingEvents;
params?: Record<string, unknown>;
}) => void;
}) => {
unmount?: (shouldTerminate?: boolean) => void;
mount?: (link: string) => void;
Expand Down
10 changes: 10 additions & 0 deletions packages/sdk/src/ui/InstallModal/InstallModal-web.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TrackingEvents } from '@metamask/sdk-communication-layer';
import packageJson from '../../../package.json';
import { MetaMaskInstaller } from '../../Platform/MetaMaskInstaller';
import { logger } from '../../utils/logger';
Expand All @@ -10,13 +11,21 @@ const sdkWebInstallModal = ({
terminate,
connectWithExtension,
preferDesktop,
onAnalyticsEvent,
}: {
link: string;
debug?: boolean;
preferDesktop?: boolean;
installer: MetaMaskInstaller;
terminate?: () => void;
connectWithExtension?: () => void;
onAnalyticsEvent: ({
event,
params,
}: {
event: TrackingEvents;
params?: Record<string, unknown>;
}) => void;
}) => {
let modalLoader: ModalLoader | null = null;
let div: HTMLDivElement | null = null;
Expand Down Expand Up @@ -94,6 +103,7 @@ const sdkWebInstallModal = ({
link,
metaMaskInstaller: installer,
onClose: unmount,
onAnalyticsEvent,
})
.catch((err) => {
console.error(`[UI: InstallModal-web: sdkWebInstallModal()]`, err);
Expand Down
Loading

0 comments on commit 1edf4b5

Please sign in to comment.