Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic login + ExtensionProvider login #12

Merged
merged 15 commits into from
Jul 25, 2024
Merged
91 changes: 54 additions & 37 deletions src/core/ProviderFactory.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { Transaction } from '@multiversx/sdk-core';
import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider';
import { ExtensionProvider } from '@multiversx/sdk-extension-provider';
import type { IDAppProviderBase } from '@multiversx/sdk-dapp-utils';
import { LoginMethodsType, LoginMethodsEnum } from '../types';

export interface IProvider {
login: (options?: { token?: string }) => Promise<any>;
export interface IProvider extends IDAppProviderBase {
init: () => Promise<boolean>;
// TODO change return type to { address: string, signature: string } and also change the return type in IDAppProviderBase.
login: (options?: { token?: string }) => Promise<string | boolean>;
logout: () => Promise<boolean>;
signTransactions: (transaction: Transaction[]) => Promise<Transaction[]>;
setAddress: (address: string) => IProvider;
setShouldShowConsentPopup?: (shouldShow: boolean) => void;
getAddress(): string | undefined;
getTokenLoginSignature(): string | undefined;
}

export interface IProviderConfig {
network: {
walletAddress: string;
};
}

export type ProviderType = LoginMethodsType;

export interface IProviderFactory {
type: ProviderTypeEnum;
type: ProviderType;
config: IProviderConfig;
address?: string;
}

export interface IProviderRecreateFactory extends IProviderFactory {
address: string;
customProvider?: IProvider;
}

export enum ProviderTypeEnum {
iframe = 'iframe',
crossWindow = 'crossWindow'
}
export const ProviderTypeEnum = {
...LoginMethodsEnum
} as const;

export class ProviderFactory {
public static async create({
public async create({
type,
config: {
network: { walletAddress }
},
address
}: IProviderFactory): Promise<IProvider> {
let createdProvider: IProvider;
config,
customProvider
}: IProviderFactory): Promise<IProvider | undefined> {
let createdProvider: IProvider | undefined = undefined;

switch (type) {
// case ProviderTypeEnum.iframe: {
Expand All @@ -47,36 +49,45 @@ export class ProviderFactory {
// break;
// }

case ProviderTypeEnum.crossWindow: {
const provider = await ProviderFactory.getCrossWindowProvider({
walletAddress
});
case ProviderTypeEnum.extension: {
const provider = await this.getExtensionProvider();
createdProvider = provider as unknown as IProvider;

createdProvider.getAddress = () => {
return provider.account.address;
}

createdProvider.getTokenLoginSignature = () => {
return provider.account.signature;
}

break;
}

default:
const provider = await ProviderFactory.getCrossWindowProvider({
case ProviderTypeEnum.crossWindow: {
const { walletAddress } = config.network;

const provider = await this.getCrossWindowProvider({
walletAddress
});
createdProvider = provider as unknown as IProvider;

break;
}
}

if (address) {
createdProvider.setAddress(address);
case ProviderTypeEnum.custom: {
createdProvider = customProvider;
break;
}

default:
break;
}

return createdProvider;
}

public static async reCreate(
config: IProviderRecreateFactory
): Promise<IProvider> {
return await ProviderFactory.create(config);
}

private static async getCrossWindowProvider({
private async getCrossWindowProvider({
walletAddress
}: Partial<IProviderConfig['network']>) {
// CrossWindowProvider.getInstance().clearInstance();
Expand All @@ -85,4 +96,10 @@ export class ProviderFactory {
provider.setWalletUrl(String(walletAddress));
return provider;
}

private async getExtensionProvider() {
const provider = ExtensionProvider.getInstance();
await provider.init();
return provider;
}
}
97 changes: 97 additions & 0 deletions src/core/methods/login/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { IProvider, IProviderFactory, ProviderFactory } from 'core/ProviderFactory';
import { NativeAuthConfigType } from 'types/nativeAuth.types';
import { nativeAuth } from 'services/nativeAuth';
import { setAddress } from 'store/actions/account';
import { setLoginMethod, setTokenLogin } from 'store/actions/loginInfo/loginInfoActions';
import { setAccountProvider } from 'core/providers/accountProvider';
import { getNativeAuthConfig } from 'services/nativeAuth/methods';

async function normalLogin(provider: IProvider) {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
await provider.login();

const address = provider.getAddress?.();

if (!address) {
throw new Error('Address not found');
}

setAddress(address);

return {
address
};
}

async function loginWithNativeToken(provider: IProvider, nativeAuthConfig: NativeAuthConfigType) {
const nativeAuthClient = nativeAuth(nativeAuthConfig);

const loginToken = await nativeAuthClient.initialize({
noCache: true
});

await provider.login({ token: loginToken });

const address = provider.getAddress?.();
const signature = provider.getTokenLoginSignature?.();

if(!address) {
throw new Error('Address not found');
}

if(!signature) {
throw new Error('Signature not found');
}

const nativeAuthToken = nativeAuthClient.getToken({
address,
token: loginToken,
signature
});

setAddress(address);
setTokenLogin({
loginToken,
signature,
nativeAuthToken,
nativeAuthConfig
});

return {
address,
signature,
nativeAuthToken,
loginToken,
nativeAuthConfig
}
}

export const login = async ({
providerConfig,
withNativeAuth
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
}: {
providerConfig: IProviderFactory,
withNativeAuth?: boolean | NativeAuthConfigType,
}) => {
const factory = new ProviderFactory();
const provider = await factory.create(providerConfig);

if(!provider) {
throw new Error('Provider not found');
}

await provider.init?.();
setAccountProvider(provider);
setLoginMethod(providerConfig.type);

if(withNativeAuth) {
if(typeof withNativeAuth === 'boolean') {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
withNativeAuth = getNativeAuthConfig(true);
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
}

const nativeAuthConfig = withNativeAuth as NativeAuthConfigType;

return await loginWithNativeToken(provider, nativeAuthConfig);
} else {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
return await normalLogin(provider);
}
}
2 changes: 1 addition & 1 deletion src/core/methods/login/webWalletLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const webWalletLogin = async ({

return newAccount;
} catch (error) {
console.error('error loging in', error);
console.error('error logging in', error);
throw error;
}
};
6 changes: 0 additions & 6 deletions src/core/methods/logout/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { LoginMethodsEnum } from 'types';
import { getAddress } from '../account/getAddress';
import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider';
import { logoutAction } from 'store/actions/sharedActions/sharedActions';
import { getWebviewToken } from '../account/getWebviewToken';
import { getAccountProvider } from 'core/providers/accountProvider';
import { getProviderType } from 'core/providers/helpers/utils';

Expand Down Expand Up @@ -35,7 +34,6 @@ export type LogoutPropsType = {
};

export async function logout(
shouldAttemptReLogin = Boolean(getWebviewToken()),
options = {
shouldBroadcastLogoutAcrossTabs: true,
hasConsentPopup: false
Expand All @@ -45,10 +43,6 @@ export async function logout(
const provider = getAccountProvider();
const providerType = getProviderType(provider);

if (shouldAttemptReLogin && provider?.relogin != null) {
return provider.relogin();
}

if (options.shouldBroadcastLogoutAcrossTabs) {
broadcastLogoutAcrossTabs(address);
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/providers/accountProvider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IDappProvider } from 'types/dappProvider.types';
import { emptyProvider } from './helpers/emptyProvider';
import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider';
import { IProvider } from 'core/ProviderFactory';

export type ProvidersType = IDappProvider | CrossWindowProvider;
export type ProvidersType = IProvider | CrossWindowProvider;

CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
let accountProvider: ProvidersType = emptyProvider;

Expand All @@ -12,6 +12,6 @@ export function setAccountProvider<TProvider extends ProvidersType>(
accountProvider = provider;
}

export function getAccountProvider(): IDappProvider {
return (accountProvider as IDappProvider) || emptyProvider;
export function getAccountProvider(): IProvider {
return (accountProvider as IProvider) || emptyProvider;
}
29 changes: 18 additions & 11 deletions src/core/providers/helpers/emptyProvider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { SignableMessage, Transaction } from '@multiversx/sdk-core';
import { IDappProvider } from 'types';
import { EngineTypes } from 'utils/walletconnect/__sdkWalletconnectProvider';
import { IProvider } from 'core/ProviderFactory';

export const DAPP_INIT_ROUTE = '/dapp/init';

const notInitializedError = (caller: string) => {
return `Unable to perform ${caller}, Provider not initialized`;
};

export class EmptyProvider implements IDappProvider {
export class EmptyProvider implements IProvider {
init(): Promise<boolean> {
return Promise.resolve(false);
}
Expand All @@ -25,10 +25,6 @@ export class EmptyProvider implements IDappProvider {
throw new Error(notInitializedError(`logout with options: ${options}`));
}

getAddress(): Promise<string> {
throw new Error(notInitializedError('getAddress'));
}

isInitialized(): boolean {
return false;
}
Expand Down Expand Up @@ -59,13 +55,12 @@ export class EmptyProvider implements IDappProvider {
);
}

signTransactions<TOptions = { callbackUrl?: string }, TResponse = []>(
transactions: [],
options?: TOptions
): Promise<TResponse> {
signTransactions<T>(
transactions: T[]
): Promise<T[]> {
throw new Error(
notInitializedError(
`signTransactions with transactions: ${transactions} options: ${options}`
`signTransactions with transactions: ${transactions}`
)
);
}
Expand Down Expand Up @@ -106,6 +101,18 @@ export class EmptyProvider implements IDappProvider {
ping?(): Promise<boolean> {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
return Promise.resolve(false);
}

setAddress(address: string): IProvider {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(notInitializedError(`setAddress with address: ${address}`));
}

getAddress(): string | undefined {
throw new Error(notInitializedError('getAddress'));
}

getTokenLoginSignature(): string | undefined {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(notInitializedError(`getSignature`));
}
}

export const emptyProvider = new EmptyProvider();
13 changes: 6 additions & 7 deletions src/core/providers/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ 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 { LoginMethodsEnum } from 'types/enums.types';
import { LoginMethodsType, LoginMethodsEnum } from 'types/enums.types';
import { WalletConnectV2Provider } from 'utils/walletconnect/__sdkWalletconnectProvider';
import { EmptyProvider } from './emptyProvider';
import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider';

export const getProviderType = <TProvider extends object>(
provider?: TProvider | null
): LoginMethodsEnum => {
): LoginMethodsType => {
switch (provider?.constructor) {
case WalletProvider:
return LoginMethodsEnum.wallet;
return LoginMethodsEnum.webhook;
case WalletConnectV2Provider:
return LoginMethodsEnum.walletconnectv2;
return LoginMethodsEnum.walletConnect;
case HWProvider:
return LoginMethodsEnum.ledger;
return LoginMethodsEnum.hardware;
case ExtensionProvider:
return LoginMethodsEnum.extension;
case MetamaskProvider:
Expand All @@ -27,8 +27,7 @@ export const getProviderType = <TProvider extends object>(
case CrossWindowProvider:
return LoginMethodsEnum.crossWindow;
case EmptyProvider:
return LoginMethodsEnum.none;
default:
return LoginMethodsEnum.extra;
return LoginMethodsEnum.none;
}
};
Loading
Loading