autsequi
Version:
Web3modal's provider layer abstraction for simple implementation of web3 wallet connections
238 lines (212 loc) • 6.73 kB
text/typescript
import * as list from '../providers';
import {
CONNECT_EVENT,
ERROR_EVENT,
INJECTED_PROVIDER_ID,
CACHED_PROVIDER_KEY,
} from '../constants';
import {
isMobile,
IProviderControllerOptions,
IProviderOptions,
IProviderDisplayWithConnector,
getLocal,
setLocal,
removeLocal,
getProviderInfoById,
getProviderDescription,
IProviderInfo,
filterMatches,
IProviderUserOptions,
getInjectedProvider,
findMatchingRequiredOptions,
} from '../helpers';
import { EventController } from './events';
export class ProviderController {
public cachedProvider: string = '';
public shouldCacheProvider: boolean = false;
public disableInjectedProvider: boolean = false;
private eventController: EventController = new EventController();
private injectedProvider: IProviderInfo | null = null;
private providers: IProviderDisplayWithConnector[] = [];
private providerOptions: IProviderOptions;
private network: string = '';
constructor(opts: IProviderControllerOptions) {
this.cachedProvider = getLocal(CACHED_PROVIDER_KEY) || '';
this.disableInjectedProvider = opts.disableInjectedProvider;
this.shouldCacheProvider = opts.cacheProvider;
this.providerOptions = opts.providerOptions;
this.network = opts.network;
this.injectedProvider = getInjectedProvider();
this.providers = Object.keys(list.connectors).map((id: string) => {
let providerInfo: IProviderInfo;
if (id === INJECTED_PROVIDER_ID) {
providerInfo = this.injectedProvider || list.providers.FALLBACK;
} else {
providerInfo = getProviderInfoById(id);
}
// parse custom display options
if (this.providerOptions[id]) {
const options = this.providerOptions[id];
if (typeof options.display !== 'undefined') {
providerInfo = {
...providerInfo,
...this.providerOptions[id].display,
};
}
}
return {
...providerInfo,
connector: list.connectors[id],
package: providerInfo.package,
};
});
// parse custom providers
Object.keys(this.providerOptions)
.filter(key => key.startsWith('custom-'))
.map(id => {
if (id && this.providerOptions[id]) {
const options = this.providerOptions[id];
if (
typeof options.display !== 'undefined' &&
typeof options.connector !== 'undefined'
) {
this.providers.push({
...list.providers.FALLBACK,
id,
...options.display,
connector: options.connector,
});
}
}
});
}
public shouldDisplayProvider(id: string) {
const provider = this.getProvider(id);
if (typeof provider !== 'undefined') {
const providerPackageOptions = this.providerOptions[id];
if (providerPackageOptions) {
const isProvided = !!providerPackageOptions.package;
if (isProvided) {
const requiredOptions = provider.package
? provider.package.required
: undefined;
if (requiredOptions && requiredOptions.length) {
const providedOptions = providerPackageOptions.options;
if (providedOptions && Object.keys(providedOptions).length) {
const matches = findMatchingRequiredOptions(
requiredOptions,
providedOptions
);
if (requiredOptions.length === matches.length) {
return true;
}
}
} else {
return true;
}
}
}
}
return false;
}
public getUserOptions = () => {
const mobile = isMobile();
const defaultProviderList = this.providers.map(({ id }) => id);
const displayInjected =
!!this.injectedProvider && !this.disableInjectedProvider;
const onlyInjected = displayInjected && mobile;
const providerList = [];
if (onlyInjected) {
providerList.push(INJECTED_PROVIDER_ID);
} else {
if (displayInjected) {
providerList.push(INJECTED_PROVIDER_ID);
}
defaultProviderList.forEach((id: string) => {
if (id !== INJECTED_PROVIDER_ID) {
const result = this.shouldDisplayProvider(id);
if (result) {
providerList.push(id);
}
}
});
}
const userOptions: IProviderUserOptions[] = [];
providerList.forEach((id: string) => {
let provider = this.getProvider(id);
if (typeof provider !== 'undefined') {
const { id, name, logo, connector } = provider;
userOptions.push({
name,
logo,
description: getProviderDescription(provider),
onClick: () => this.connectTo(id, connector),
});
}
});
return userOptions;
};
public getProvider(id: string) {
return filterMatches<IProviderDisplayWithConnector>(
this.providers,
x => x.id === id,
undefined
);
}
public getProviderOption(id: string, key: string) {
return this.providerOptions &&
this.providerOptions[id] &&
this.providerOptions[id][key]
? this.providerOptions[id][key]
: {};
}
public clearCachedProvider() {
this.cachedProvider = '';
removeLocal(CACHED_PROVIDER_KEY);
}
public setCachedProvider(id: string) {
this.cachedProvider = id;
setLocal(CACHED_PROVIDER_KEY, id);
}
public connectTo = async (
id: string,
connector: (providerPackage: any, opts: any) => Promise<any>
) => {
try {
const providerPackage = this.getProviderOption(id, 'package');
const providerOptions = this.getProviderOption(id, 'options');
const opts = { network: this.network || undefined, ...providerOptions };
const provider = await connector(providerPackage, opts);
this.eventController.trigger(CONNECT_EVENT, provider);
if (this.shouldCacheProvider && this.cachedProvider !== id) {
this.setCachedProvider(id);
}
} catch (error) {
this.eventController.trigger(ERROR_EVENT);
}
};
public async connectToCachedProvider() {
const provider = this.getProvider(this.cachedProvider);
if (typeof provider !== 'undefined') {
await this.connectTo(provider.id, provider.connector);
}
}
public on(event: string, callback: (result: any) => void): () => void {
this.eventController.on({
event,
callback,
});
return () =>
this.eventController.off({
event,
callback,
});
}
public off(event: string, callback?: (result: any) => void): void {
this.eventController.off({
event,
callback,
});
}
}