From 660fb1c18c168ec95330021365a87dd218c2732b Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 16:34:09 +0300 Subject: [PATCH] Create provider (#39) --- src/core/methods/initApp/initApp.ts | 12 +++++- src/core/methods/initApp/initApp.types.ts | 2 + .../DappProvider/helpers/logout/logout.ts | 4 +- .../helpers/signMessage/signMessage.ts | 4 +- src/core/providers/ProviderFactory.ts | 37 ++++++++-------- src/core/providers/helpers/getConfig.ts | 42 ++++++++++++------- .../helpers/ledger/createLedgerProvider.ts | 14 +++---- src/core/providers/helpers/restoreProvider.ts | 4 +- src/core/providers/helpers/utils.ts | 33 --------------- .../providers/types/providerFactory.types.ts | 36 ++++++++++------ .../actions/loginInfo/loginInfoActions.ts | 4 +- .../actions/sharedActions/sharedActions.ts | 6 ++- src/store/slices/loginInfo/loginInfo.types.ts | 6 ++- 13 files changed, 103 insertions(+), 101 deletions(-) delete mode 100644 src/core/providers/helpers/utils.ts diff --git a/src/core/methods/initApp/initApp.ts b/src/core/methods/initApp/initApp.ts index c83d67e..83ae383 100644 --- a/src/core/methods/initApp/initApp.ts +++ b/src/core/methods/initApp/initApp.ts @@ -1,4 +1,6 @@ +import { safeWindow } from 'constants/index'; import { restoreProvider } from 'core/providers/helpers/restoreProvider'; +import { ProviderFactory } from 'core/providers/ProviderFactory'; import { getDefaultNativeAuthConfig } from 'services/nativeAuth/methods/getDefaultNativeAuthConfig'; import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; import { initializeNetwork } from 'store/actions'; @@ -30,7 +32,8 @@ const defaultInitAppProps = { * */ export async function initApp({ storage = defaultInitAppProps.storage, - dAppConfig + dAppConfig, + customProviders }: InitAppType) { initStore(storage.getStorageCallback); @@ -57,6 +60,13 @@ export async function initApp({ const isLoggedIn = getIsLoggedIn(); + const usedProviders = [ + ...((safeWindow as any)?.multiversx?.providers || []), + ...(customProviders || []) + ]; + + ProviderFactory.customProviders(usedProviders || []); + if (isLoggedIn) { await restoreProvider(); await registerWebsocketListener(); diff --git a/src/core/methods/initApp/initApp.types.ts b/src/core/methods/initApp/initApp.types.ts index d53d8e3..69dbc89 100644 --- a/src/core/methods/initApp/initApp.types.ts +++ b/src/core/methods/initApp/initApp.types.ts @@ -1,3 +1,4 @@ +import { ICustomProvider } from 'core/providers/types/providerFactory.types'; import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; import { StorageCallback } from 'store/storage'; import { EnvironmentsEnum } from 'types/enums.types'; @@ -49,4 +50,5 @@ export type InitAppType = { getStorageCallback: StorageCallback; }; dAppConfig: DappConfigType; + customProviders?: ICustomProvider[]; }; diff --git a/src/core/providers/DappProvider/helpers/logout/logout.ts b/src/core/providers/DappProvider/helpers/logout/logout.ts index 2e6476c..73f3cc9 100644 --- a/src/core/providers/DappProvider/helpers/logout/logout.ts +++ b/src/core/providers/DappProvider/helpers/logout/logout.ts @@ -1,5 +1,4 @@ import { getAddress } from 'core/methods/account/getAddress'; -import { getProviderType } from 'core/providers/helpers/utils'; import { IProvider, ProviderTypeEnum @@ -48,7 +47,6 @@ export async function logout({ } }: IProviderLogout) { let address = getAddress(); - const providerType = getProviderType(provider); if (options.shouldBroadcastLogoutAcrossTabs) { broadcastLogoutAcrossTabs(address); @@ -59,7 +57,7 @@ export async function logout({ if ( options.hasConsentPopup && - providerType === ProviderTypeEnum.crossWindow + provider.getType() === ProviderTypeEnum.crossWindow ) { (provider as unknown as CrossWindowProvider).setShouldShowConsentPopup( true diff --git a/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts b/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts index 7cf3e8e..ed21f05 100644 --- a/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts +++ b/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts @@ -1,6 +1,5 @@ import { Message, Address } from '@multiversx/sdk-core'; import { getAddress } from 'core/methods/account/getAddress'; -import { getProviderType } from 'core/providers/helpers/utils'; import { IProvider, ProviderTypeEnum @@ -22,7 +21,6 @@ export async function signMessage({ options }: SignMessageType): Promise> { const address = getAddress(); - const providerType = getProviderType(provider); const messageToSign = new Message({ address: new Address(address), @@ -31,7 +29,7 @@ export async function signMessage({ if ( options?.hasConsentPopup && - providerType === ProviderTypeEnum.crossWindow + provider.getType() === ProviderTypeEnum.crossWindow ) { (provider as unknown as CrossWindowProvider).setShouldShowConsentPopup( true diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index d7427be..aa37f76 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -11,19 +11,26 @@ import { getConfig } from './helpers/getConfig'; import { createIframeProvider } from './helpers/iframe/createIframeProvider'; import { createLedgerProvider } from './helpers/ledger/createLedgerProvider'; import { + ICustomProvider, IProvider, IProviderFactory, ProviderTypeEnum } from './types/providerFactory.types'; export class ProviderFactory { - public async create({ + private static _customProviders: ICustomProvider[] = []; + + public static customProviders(providers: ICustomProvider[]) { + this._customProviders = providers; + } + + public static async create({ type, - config: userConfig, - customProvider + config: userConfig }: IProviderFactory): Promise { let createdProvider: IProvider | null = null; - const { account, ui } = await getConfig(userConfig); + const config = await getConfig(userConfig); + const { account, UI } = config; switch (type) { case ProviderTypeEnum.extension: { @@ -47,10 +54,7 @@ export class ProviderFactory { } case ProviderTypeEnum.ledger: { - const ledgerProvider = await createLedgerProvider( - ui.ledger.eventBus, - ui.ledger.mount - ); + const ledgerProvider = await createLedgerProvider(UI.ledger.mount); if (!ledgerProvider) { throw new Error('Unable to create ledger provider'); @@ -106,16 +110,15 @@ export class ProviderFactory { break; } - case ProviderTypeEnum.custom: { - if (!customProvider) { - throw new Error('Unable to create custom provider provider'); - } - createdProvider = customProvider; + default: { + this._customProviders.forEach(async (customProvider) => { + if (customProvider.type === type) { + createdProvider = await customProvider.constructor(config); + createdProvider.getType = () => type; + } + }); break; } - - default: - break; } if (!createdProvider) { @@ -125,7 +128,7 @@ export class ProviderFactory { const dappProvider = new DappProvider(createdProvider); setAccountProvider(dappProvider); - setProviderType(type); + setProviderType(type as ProviderTypeEnum); return dappProvider; } diff --git a/src/core/providers/helpers/getConfig.ts b/src/core/providers/helpers/getConfig.ts index 70a2b92..e937da4 100644 --- a/src/core/providers/helpers/getConfig.ts +++ b/src/core/providers/helpers/getConfig.ts @@ -3,36 +3,46 @@ import { defineCustomElements } from '@multiversx/sdk-dapp-core-ui/loader'; import { safeWindow } from 'constants/index'; import { IProviderConfig, + IProviderConfigUI, ProviderTypeEnum } from '../types/providerFactory.types'; -export const getConfig = async (config: IProviderConfig = {}) => { - if (!safeWindow.document) { - return config; +const UI: IProviderConfigUI = { + [ProviderTypeEnum.ledger]: { + mount: () => { + throw new Error('mount not implemented'); + } } +}; + +const defaultConfig = { UI }; - defineCustomElements(safeWindow); - const ledgerModalElement = document.createElement( - 'ledger-connect-modal' - ) as LedgerConnectModal; - document.body.appendChild(ledgerModalElement); - await customElements.whenDefined('ledger-connect-modal'); - const eventBus = await ledgerModalElement.getEventBus(); +export const getConfig = async (config: IProviderConfig = defaultConfig) => { + if (!safeWindow.document) { + return { ...defaultConfig, ...config }; + } - const ui = { + const UI = { [ProviderTypeEnum.ledger]: { - eventBus, - mount: () => { + mount: async () => { + defineCustomElements(safeWindow); + const ledgerModalElement = document.createElement( + 'ledger-connect-modal' + ) as LedgerConnectModal; + document.body.appendChild(ledgerModalElement); + + const eventBus = await ledgerModalElement.getEventBus(); + return eventBus; } } }; return { ...config, - ui: { - ...ui, - ...config.ui + UI: { + ...defaultConfig.UI, + ...UI } }; }; diff --git a/src/core/providers/helpers/ledger/createLedgerProvider.ts b/src/core/providers/helpers/ledger/createLedgerProvider.ts index c200e7a..d2e476c 100644 --- a/src/core/providers/helpers/ledger/createLedgerProvider.ts +++ b/src/core/providers/helpers/ledger/createLedgerProvider.ts @@ -18,17 +18,17 @@ import { ILedgerAccount } from './ledger.types'; const failInitializeErrorText = 'Check if the MultiversX App is open on Ledger'; export async function createLedgerProvider( - eventBus: IEventBus, - mount: () => void + mount: () => Promise ): Promise { - if (!eventBus) { - throw new Error('Event bus not provided for Ledger provider'); - } - const shouldInitiateLogin = !getIsLoggedIn(); + let eventBus: IEventBus | undefined; if (shouldInitiateLogin) { - mount?.(); + eventBus = await mount?.(); + } + + if (!eventBus) { + throw new Error('Event bus not provided for Ledger provider'); } const manager = LedgerConnectStateManager.getInstance(eventBus); diff --git a/src/core/providers/helpers/restoreProvider.ts b/src/core/providers/helpers/restoreProvider.ts index 3e352ef..9a4ff0c 100644 --- a/src/core/providers/helpers/restoreProvider.ts +++ b/src/core/providers/helpers/restoreProvider.ts @@ -19,9 +19,7 @@ export async function restoreProvider() { } }; - const factory = new ProviderFactory(); - - const provider = await factory.create({ + const provider = await ProviderFactory.create({ type, config }); diff --git a/src/core/providers/helpers/utils.ts b/src/core/providers/helpers/utils.ts deleted file mode 100644 index 89e50f3..0000000 --- a/src/core/providers/helpers/utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ExtensionProvider } from '@multiversx/sdk-extension-provider'; -import { HWProvider } from '@multiversx/sdk-hw-provider'; -import { MetamaskProvider } from '@multiversx/sdk-metamask-provider/out/metamaskProvider'; -import { OperaProvider } from '@multiversx/sdk-opera-provider'; -import { WalletProvider } from '@multiversx/sdk-web-wallet-provider'; -import { ProviderTypeEnum } from 'core/providers/types/providerFactory.types'; -import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; -import { WalletConnectV2Provider } from 'utils/walletconnect/__sdkWalletconnectProvider'; -import { EmptyProvider } from './emptyProvider'; - -export function getProviderType( - provider?: TProvider | null -): ProviderTypeEnum { - switch (provider?.constructor) { - case WalletProvider: - return ProviderTypeEnum.webhook; // TODO: remove? - case WalletConnectV2Provider: - return ProviderTypeEnum.walletConnect; - case HWProvider: - return ProviderTypeEnum.ledger; - case ExtensionProvider: - return ProviderTypeEnum.extension; - case MetamaskProvider: - return ProviderTypeEnum.metamask; - case OperaProvider: - return ProviderTypeEnum.opera; - case CrossWindowProvider: - return ProviderTypeEnum.crossWindow; - case EmptyProvider: - default: - return ProviderTypeEnum.none; - } -} diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index e95466c..2063592 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -1,7 +1,8 @@ import type { IDAppProviderBase } from '@multiversx/sdk-dapp-utils'; // @ts-ignore -export interface IProvider extends IDAppProviderBase { +export interface IProvider + extends IDAppProviderBase { init: () => Promise; login: (options?: { callbackUrl?: string; token?: string }) => Promise<{ address: string; @@ -12,7 +13,7 @@ export interface IProvider extends IDAppProviderBase { }>; logout: () => Promise; setShouldShowConsentPopup?: (shouldShow: boolean) => void; - getType: () => ProviderTypeEnum; + getType: () => T[keyof T] | string; getAddress(): Promise; // TODO will be removed as soon as the new login method is implemented in the same way for all providers getTokenLoginSignature(): string | undefined; @@ -26,16 +27,17 @@ export interface IEventBus { unsubscribe(event: string, callback: Function): void; } +export interface IProviderConfigUI { + ledger: { + mount: () => Promise; + }; +} + export interface IProviderConfig { account?: { address: string; }; - ui?: { - ledger: { - eventBus: IEventBus; - mount: () => void; - }; - }; + UI?: IProviderConfigUI; } export enum ProviderTypeEnum { @@ -47,13 +49,21 @@ export enum ProviderTypeEnum { opera = 'opera', metamask = 'metamask', passkey = 'passkey', - webhook = 'webhook', - custom = 'custom', none = '' } -export interface IProviderFactory { - type: ProviderTypeEnum; +export interface IProviderFactory< + T extends ProviderTypeEnum = ProviderTypeEnum +> { + type: T[keyof T]; config?: IProviderConfig; - customProvider?: IProvider; +} + +export interface ICustomProvider< + T extends ProviderTypeEnum = ProviderTypeEnum +> { + name: string; + type: T[keyof T]; + icon: string; + constructor: (config: IProviderConfig) => Promise; } diff --git a/src/store/actions/loginInfo/loginInfoActions.ts b/src/store/actions/loginInfo/loginInfoActions.ts index e9ae8ff..c8a610f 100644 --- a/src/store/actions/loginInfo/loginInfoActions.ts +++ b/src/store/actions/loginInfo/loginInfoActions.ts @@ -7,7 +7,9 @@ import { import { getStore } from 'store/store'; import { TokenLoginType } from 'types/login.types'; -export const setProviderType = (providerType: ProviderTypeEnum) => +export const setProviderType = ( + providerType: T +) => getStore().setState(({ loginInfo: state }) => { state.providerType = providerType; }); diff --git a/src/store/actions/sharedActions/sharedActions.ts b/src/store/actions/sharedActions/sharedActions.ts index 530925f..fcda2aa 100644 --- a/src/store/actions/sharedActions/sharedActions.ts +++ b/src/store/actions/sharedActions/sharedActions.ts @@ -4,9 +4,11 @@ import { resetStore } from 'store/middleware/logoutMiddleware'; import { getStore } from '../../store'; export const logoutAction = () => getStore().setState(resetStore); -export interface LoginActionPayloadType { +export interface LoginActionPayloadType< + T extends ProviderTypeEnum = ProviderTypeEnum +> { address: string; - providerType: ProviderTypeEnum; + providerType: T[keyof T]; } export const loginAction = ({ diff --git a/src/store/slices/loginInfo/loginInfo.types.ts b/src/store/slices/loginInfo/loginInfo.types.ts index 118b475..4560b12 100644 --- a/src/store/slices/loginInfo/loginInfo.types.ts +++ b/src/store/slices/loginInfo/loginInfo.types.ts @@ -17,8 +17,10 @@ export interface LoginInfoType { expires: number; } -export interface LoginInfoSliceType { - providerType: ProviderTypeEnum | null; +export interface LoginInfoSliceType< + T extends ProviderTypeEnum = ProviderTypeEnum +> { + providerType: T[keyof T] | null; walletConnectLogin: WalletConnectLoginType | null; ledgerLogin: LedgerLoginType | null; tokenLogin: TokenLoginType | null;