From e71c11e72b7283f0d01914e09f585586139b1e63 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 30 Dec 2021 10:19:46 +0100 Subject: [PATCH] Add bundle exports for packages with detect (#951) --- packages/extension-base/src/bundle.ts | 4 + packages/extension-base/src/index.ts | 2 +- packages/extension-chains/src/bundle.ts | 87 +++++++ packages/extension-chains/src/index.ts | 85 +----- .../extension-compat-metamask/src/bundle.ts | 102 ++++++++ .../extension-compat-metamask/src/index.ts | 100 +------ packages/extension-dapp/src/bundle.ts | 244 ++++++++++++++++++ packages/extension-dapp/src/index.ts | 242 +---------------- packages/extension-inject/src/bundle.ts | 24 ++ packages/extension-inject/src/index.ts | 22 +- 10 files changed, 466 insertions(+), 446 deletions(-) create mode 100644 packages/extension-base/src/bundle.ts create mode 100644 packages/extension-chains/src/bundle.ts create mode 100644 packages/extension-compat-metamask/src/bundle.ts create mode 100644 packages/extension-dapp/src/bundle.ts create mode 100644 packages/extension-inject/src/bundle.ts diff --git a/packages/extension-base/src/bundle.ts b/packages/extension-base/src/bundle.ts new file mode 100644 index 00000000..5652507b --- /dev/null +++ b/packages/extension-base/src/bundle.ts @@ -0,0 +1,4 @@ +// Copyright 2019-2021 @polkadot/extension-base authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export { packageInfo } from './packageInfo'; diff --git a/packages/extension-base/src/index.ts b/packages/extension-base/src/index.ts index 876701e9..174e9f35 100644 --- a/packages/extension-base/src/index.ts +++ b/packages/extension-base/src/index.ts @@ -3,4 +3,4 @@ import './detectPackage'; -export { packageInfo } from './packageInfo'; +export * from './bundle'; diff --git a/packages/extension-chains/src/bundle.ts b/packages/extension-chains/src/bundle.ts new file mode 100644 index 00000000..79fc343b --- /dev/null +++ b/packages/extension-chains/src/bundle.ts @@ -0,0 +1,87 @@ +// Copyright 2019-2021 @polkadot/extension-chains authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { MetadataDef } from '@polkadot/extension-inject/types'; +import type { ChainProperties } from '@polkadot/types/interfaces'; +import type { Chain } from './types'; + +import { Metadata, TypeRegistry } from '@polkadot/types'; +import { base64Decode } from '@polkadot/util-crypto'; + +export { packageInfo } from './packageInfo'; + +// imports chain details, generally metadata. For the generation of these, +// inside the api, run `yarn chain:info --ws ` + +const definitions = new Map( + // [kusama].map((def) => [def.genesisHash, def]) +); + +const expanded = new Map(); + +export function metadataExpand (definition: MetadataDef, isPartial = false): Chain { + const cached = expanded.get(definition.genesisHash); + + if (cached && cached.specVersion === definition.specVersion) { + return cached; + } + + const { chain, genesisHash, icon, metaCalls, specVersion, ss58Format, tokenDecimals, tokenSymbol, types, userExtensions } = definition; + const registry = new TypeRegistry(); + + if (!isPartial) { + registry.register(types); + } + + registry.setChainProperties(registry.createType('ChainProperties', { + ss58Format, + tokenDecimals, + tokenSymbol + }) as unknown as ChainProperties); + + const hasMetadata = !!metaCalls && !isPartial; + + if (hasMetadata) { + registry.setMetadata(new Metadata(registry, base64Decode(metaCalls)), undefined, userExtensions); + } + + const isUnknown = genesisHash === '0x'; + + const result = { + definition, + genesisHash: isUnknown + ? undefined + : genesisHash, + hasMetadata, + icon: icon || 'substrate', + isUnknown, + name: chain, + registry, + specVersion, + ss58Format, + tokenDecimals, + tokenSymbol + }; + + if (result.genesisHash && !isPartial) { + expanded.set(result.genesisHash, result); + } + + return result; +} + +export function findChain (definitions: MetadataDef[], genesisHash?: string | null): Chain | null { + const def = definitions.find((def) => def.genesisHash === genesisHash); + + return def + ? metadataExpand(def) + : null; +} + +export function addMetadata (def: MetadataDef): void { + definitions.set(def.genesisHash, def); +} + +export function knownMetadata (): MetadataDef[] { + return [...definitions.values()]; +} diff --git a/packages/extension-chains/src/index.ts b/packages/extension-chains/src/index.ts index 27cec0ae..c32a091a 100644 --- a/packages/extension-chains/src/index.ts +++ b/packages/extension-chains/src/index.ts @@ -3,87 +3,4 @@ import './detectPackage'; -import type { MetadataDef } from '@polkadot/extension-inject/types'; -import type { ChainProperties } from '@polkadot/types/interfaces'; -import type { Chain } from './types'; - -import { Metadata, TypeRegistry } from '@polkadot/types'; -import { base64Decode } from '@polkadot/util-crypto'; - -export { packageInfo } from './packageInfo'; - -// imports chain details, generally metadata. For the generation of these, -// inside the api, run `yarn chain:info --ws ` - -const definitions = new Map( - // [kusama].map((def) => [def.genesisHash, def]) -); - -const expanded = new Map(); - -export function metadataExpand (definition: MetadataDef, isPartial = false): Chain { - const cached = expanded.get(definition.genesisHash); - - if (cached && cached.specVersion === definition.specVersion) { - return cached; - } - - const { chain, genesisHash, icon, metaCalls, specVersion, ss58Format, tokenDecimals, tokenSymbol, types, userExtensions } = definition; - const registry = new TypeRegistry(); - - if (!isPartial) { - registry.register(types); - } - - registry.setChainProperties(registry.createType('ChainProperties', { - ss58Format, - tokenDecimals, - tokenSymbol - }) as unknown as ChainProperties); - - const hasMetadata = !!metaCalls && !isPartial; - - if (hasMetadata) { - registry.setMetadata(new Metadata(registry, base64Decode(metaCalls)), undefined, userExtensions); - } - - const isUnknown = genesisHash === '0x'; - - const result = { - definition, - genesisHash: isUnknown - ? undefined - : genesisHash, - hasMetadata, - icon: icon || 'substrate', - isUnknown, - name: chain, - registry, - specVersion, - ss58Format, - tokenDecimals, - tokenSymbol - }; - - if (result.genesisHash && !isPartial) { - expanded.set(result.genesisHash, result); - } - - return result; -} - -export function findChain (definitions: MetadataDef[], genesisHash?: string | null): Chain | null { - const def = definitions.find((def) => def.genesisHash === genesisHash); - - return def - ? metadataExpand(def) - : null; -} - -export function addMetadata (def: MetadataDef): void { - definitions.set(def.genesisHash, def); -} - -export function knownMetadata (): MetadataDef[] { - return [...definitions.values()]; -} +export * from './bundle'; diff --git a/packages/extension-compat-metamask/src/bundle.ts b/packages/extension-compat-metamask/src/bundle.ts new file mode 100644 index 00000000..a3ba2d70 --- /dev/null +++ b/packages/extension-compat-metamask/src/bundle.ts @@ -0,0 +1,102 @@ +// Copyright 2019-2020 @polkadot/extension-compat-metamask authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Injected, InjectedAccount, InjectedWindow } from '@polkadot/extension-inject/types'; +import type { SignerPayloadRaw, SignerResult } from '@polkadot/types/types'; +import type { HexString } from '@polkadot/util/types'; + +import detectEthereumProvider from '@metamask/detect-provider'; +import Web3 from 'web3'; + +import { assert } from '@polkadot/util'; + +export { packageInfo } from './packageInfo'; + +interface RequestArguments { + method: string; + params?: unknown[]; +} + +interface EthRpcSubscription { + unsubscribe: () => void +} + +interface EthereumProvider { + request: (args: RequestArguments) => Promise; + isMetaMask: boolean; + on: (name: string, cb: (value: unknown) => void) => EthRpcSubscription; +} + +interface Web3Window extends InjectedWindow { + // this is injected by metaMask + ethereum: unknown; +} + +function isMetaMaskProvider (prov: unknown): EthereumProvider { + assert(prov && (prov as EthereumProvider).isMetaMask, 'Injected provider is not MetaMask'); + + return (prov as EthereumProvider); +} + +// transform the Web3 accounts into a simple address/name array +function transformAccounts (accounts: string[]): InjectedAccount[] { + return accounts.map((address, i) => ({ + address, + name: `MetaMask Address #${i}`, + type: 'ethereum' + })); +} + +// add a compat interface of metaMaskSource to window.injectedWeb3 +function injectMetaMaskWeb3 (win: Web3Window): void { + // decorate the compat interface + win.injectedWeb3.Web3Source = { + enable: async (): Promise => { + const providerRaw = await detectEthereumProvider({ mustBeMetaMask: true }); + const provider = isMetaMaskProvider(providerRaw); + + await provider.request({ method: 'eth_requestAccounts' }); + + return { + accounts: { + get: async (): Promise => { + const response = (await provider.request({ method: 'eth_requestAccounts' })) as string[]; + + return transformAccounts(response); + }, + subscribe: (cb: (accounts: InjectedAccount[]) => void): (() => void) => { + const sub = provider.on('accountsChanged', (accounts): void => { + cb(transformAccounts(accounts as string[])); + }); + // TODO: add onchainchanged + + return (): void => { + sub.unsubscribe(); + }; + } + }, + signer: { + signRaw: async (raw: SignerPayloadRaw): Promise => { + const signature = (await provider.request({ method: 'eth_sign', params: [raw.address, Web3.utils.sha3(raw.data)] })) as HexString; + + return { id: 0, signature }; + } + } + }; + }, + version: '0' // TODO: win.ethereum.version + }; +} + +export default function initMetaMask (): Promise { + return new Promise((resolve): void => { + const win = window as Window & Web3Window; + + if (win.ethereum) { + injectMetaMaskWeb3(win); + resolve(true); + } else { + resolve(false); + } + }); +} diff --git a/packages/extension-compat-metamask/src/index.ts b/packages/extension-compat-metamask/src/index.ts index 3f3a5655..211fed71 100644 --- a/packages/extension-compat-metamask/src/index.ts +++ b/packages/extension-compat-metamask/src/index.ts @@ -3,102 +3,4 @@ import './detectPackage'; -import type { Injected, InjectedAccount, InjectedWindow } from '@polkadot/extension-inject/types'; -import type { SignerPayloadRaw, SignerResult } from '@polkadot/types/types'; -import type { HexString } from '@polkadot/util/types'; - -import detectEthereumProvider from '@metamask/detect-provider'; -import Web3 from 'web3'; - -import { assert } from '@polkadot/util'; - -export { packageInfo } from './packageInfo'; - -interface RequestArguments { - method: string; - params?: unknown[]; -} - -interface EthRpcSubscription { - unsubscribe: () => void -} - -interface EthereumProvider { - request: (args: RequestArguments) => Promise; - isMetaMask: boolean; - on: (name: string, cb: (value: unknown) => void) => EthRpcSubscription; -} - -interface Web3Window extends InjectedWindow { - // this is injected by metaMask - ethereum: unknown; -} - -function isMetaMaskProvider (prov: unknown): EthereumProvider { - assert(prov && (prov as EthereumProvider).isMetaMask, 'Injected provider is not MetaMask'); - - return (prov as EthereumProvider); -} - -// transform the Web3 accounts into a simple address/name array -function transformAccounts (accounts: string[]): InjectedAccount[] { - return accounts.map((address, i) => ({ - address, - name: `MetaMask Address #${i}`, - type: 'ethereum' - })); -} - -// add a compat interface of metaMaskSource to window.injectedWeb3 -function injectMetaMaskWeb3 (win: Web3Window): void { - // decorate the compat interface - win.injectedWeb3.Web3Source = { - enable: async (): Promise => { - const providerRaw = await detectEthereumProvider({ mustBeMetaMask: true }); - const provider = isMetaMaskProvider(providerRaw); - - await provider.request({ method: 'eth_requestAccounts' }); - - return { - accounts: { - get: async (): Promise => { - const response = (await provider.request({ method: 'eth_requestAccounts' })) as string[]; - - return transformAccounts(response); - }, - subscribe: (cb: (accounts: InjectedAccount[]) => void): (() => void) => { - const sub = provider.on('accountsChanged', (accounts): void => { - cb(transformAccounts(accounts as string[])); - }); - // TODO: add onchainchanged - - return (): void => { - sub.unsubscribe(); - }; - } - }, - signer: { - signRaw: async (raw: SignerPayloadRaw): Promise => { - const signature = (await provider.request({ method: 'eth_sign', params: [raw.address, Web3.utils.sha3(raw.data)] })) as HexString; - - return { id: 0, signature }; - } - } - }; - }, - version: '0' // TODO: win.ethereum.version - }; -} - -export default function initMetaMask (): Promise { - return new Promise((resolve): void => { - const win = window as Window & Web3Window; - - if (win.ethereum) { - injectMetaMaskWeb3(win); - resolve(true); - } else { - resolve(false); - } - }); -} +export * from './bundle'; diff --git a/packages/extension-dapp/src/bundle.ts b/packages/extension-dapp/src/bundle.ts new file mode 100644 index 00000000..ad853cee --- /dev/null +++ b/packages/extension-dapp/src/bundle.ts @@ -0,0 +1,244 @@ +// Copyright 2019-2021 @polkadot/extension-dapp authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Injected, InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedExtensionInfo, InjectedProviderWithMeta, InjectedWindow, ProviderList, Unsubcall, Web3AccountsOptions } from '@polkadot/extension-inject/types'; + +import { u8aEq } from '@polkadot/util'; +import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; + +import { documentReadyPromise } from './util'; + +// expose utility functions +export { packageInfo } from './packageInfo'; +export { unwrapBytes, wrapBytes } from './wrapBytes'; + +// just a helper (otherwise we cast all-over, so shorter and more readable) +const win = window as Window & InjectedWindow; + +// don't clobber the existing object, but ensure non-undefined +win.injectedWeb3 = win.injectedWeb3 || {}; + +// true when anything has been injected and is available +function web3IsInjected (): boolean { + return Object.keys(win.injectedWeb3).length !== 0; +} + +// helper to throw a consistent error when not enabled +function throwError (method: string): never { + throw new Error(`${method}: web3Enable(originName) needs to be called before ${method}`); +} + +// internal helper to map from Array -> Array +function mapAccounts (source: string, list: InjectedAccount[], ss58Format?: number): InjectedAccountWithMeta[] { + return list.map(({ address, genesisHash, name, type }): InjectedAccountWithMeta => { + const encodedAddress = address.length === 42 ? address : encodeAddress(decodeAddress(address), ss58Format); + + return ({ + address: encodedAddress, + meta: { genesisHash, name, source }, + type + }); + }); +} + +// have we found a properly constructed window.injectedWeb3 +let isWeb3Injected = web3IsInjected(); + +// we keep the last promise created around (for queries) +let web3EnablePromise: Promise | null = null; + +export { isWeb3Injected, web3EnablePromise }; + +function getWindowExtensions (originName: string): Promise<[InjectedExtensionInfo, Injected | void][]> { + return Promise.all( + Object.entries(win.injectedWeb3).map( + ([name, { enable, version }]): Promise<[InjectedExtensionInfo, Injected | void]> => + Promise.all([ + Promise.resolve({ name, version }), + enable(originName).catch((error: Error): void => { + console.error(`Error initializing ${name}: ${error.message}`); + }) + ]) + ) + ); +} + +// enables all the providers found on the injected window interface +export function web3Enable (originName: string, compatInits: (() => Promise)[] = []): Promise { + if (!originName) { + throw new Error('You must pass a name for your app to the web3Enable function'); + } + + const initCompat = compatInits.length + ? Promise.all(compatInits.map((c) => c().catch(() => false))) + : Promise.resolve([true]); + + web3EnablePromise = documentReadyPromise( + (): Promise => + initCompat.then(() => + getWindowExtensions(originName) + .then((values): InjectedExtension[] => + values + .filter((value): value is [InjectedExtensionInfo, Injected] => !!value[1]) + .map(([info, ext]): InjectedExtension => { + // if we don't have an accounts subscriber, add a single-shot version + if (!ext.accounts.subscribe) { + ext.accounts.subscribe = (cb: (accounts: InjectedAccount[]) => void | Promise): Unsubcall => { + ext.accounts.get().then(cb).catch(console.error); + + return (): void => { + // no ubsubscribe needed, this is a single-shot + }; + }; + } + + return { ...info, ...ext }; + }) + ) + .catch((): InjectedExtension[] => []) + .then((values): InjectedExtension[] => { + const names = values.map(({ name, version }): string => `${name}/${version}`); + + isWeb3Injected = web3IsInjected(); + console.log(`web3Enable: Enabled ${values.length} extension${values.length !== 1 ? 's' : ''}: ${names.join(', ')}`); + + return values; + }) + ) + ); + + return web3EnablePromise; +} + +// retrieve all the accounts across all providers +export async function web3Accounts ({ accountType, ss58Format }: Web3AccountsOptions = {}): Promise { + if (!web3EnablePromise) { + return throwError('web3Accounts'); + } + + const accounts: InjectedAccountWithMeta[] = []; + const injected = await web3EnablePromise; + + const retrieved = await Promise.all( + injected.map(async ({ accounts, name: source }): Promise => { + try { + const list = await accounts.get(); + + return mapAccounts(source, list.filter(({ type }) => type && accountType ? accountType.includes(type) : true), ss58Format); + } catch (error) { + // cannot handle this one + return []; + } + }) + ); + + retrieved.forEach((result): void => { + accounts.push(...result); + }); + + const addresses = accounts.map(({ address }) => address); + + console.log(`web3Accounts: Found ${accounts.length} address${accounts.length !== 1 ? 'es' : ''}: ${addresses.join(', ')}`); + + return accounts; +} + +export async function web3AccountsSubscribe (cb: (accounts: InjectedAccountWithMeta[]) => void | Promise, { ss58Format }: Web3AccountsOptions = {}): Promise { + if (!web3EnablePromise) { + return throwError('web3AccountsSubscribe'); + } + + const accounts: Record = {}; + + const triggerUpdate = (): void | Promise => + cb( + Object.entries(accounts).reduce( + (result: InjectedAccountWithMeta[], [source, list]): InjectedAccountWithMeta[] => { + result.push(...mapAccounts(source, list, ss58Format)); + + return result; + }, + [] + ) + ); + + const unsubs = (await web3EnablePromise).map( + ({ accounts: { subscribe }, name: source }): Unsubcall => + subscribe((result): void => { + accounts[source] = result; + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + triggerUpdate(); + }) + ); + + return (): void => { + unsubs.forEach((unsub): void => { + unsub(); + }); + }; +} + +// find a specific provider based on the name +export async function web3FromSource (source: string): Promise { + if (!web3EnablePromise) { + return throwError('web3FromSource'); + } + + const sources = await web3EnablePromise; + const found = source && sources.find(({ name }): boolean => name === source); + + if (!found) { + throw new Error(`web3FromSource: Unable to find an injected ${source}`); + } + + return found; +} + +// find a specific provider based on an address +export async function web3FromAddress (address: string): Promise { + if (!web3EnablePromise) { + return throwError('web3FromAddress'); + } + + const accounts = await web3Accounts(); + let found: InjectedAccountWithMeta | undefined; + + if (address) { + const accountU8a = decodeAddress(address); + + found = accounts.find((account): boolean => u8aEq(decodeAddress(account.address), accountU8a)); + } + + if (!found) { + throw new Error(`web3FromAddress: Unable to find injected ${address}`); + } + + return web3FromSource(found.meta.source); +} + +// retrieve all providers exposed by one source +export async function web3ListRpcProviders (source: string): Promise { + const { provider } = await web3FromSource(source); + + if (!provider) { + console.warn(`Extension ${source} does not expose any provider`); + + return null; + } + + return provider.listProviders(); +} + +// retrieve all providers exposed by one source +export async function web3UseRpcProvider (source: string, key: string): Promise { + const { provider } = await web3FromSource(source); + + if (!provider) { + throw new Error(`Extension ${source} does not expose any provider`); + } + + const meta = await provider.startProvider(key); + + return { meta, provider }; +} diff --git a/packages/extension-dapp/src/index.ts b/packages/extension-dapp/src/index.ts index 43cc01c6..ff3eb27b 100644 --- a/packages/extension-dapp/src/index.ts +++ b/packages/extension-dapp/src/index.ts @@ -3,244 +3,4 @@ import './detectPackage'; -import type { Injected, InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedExtensionInfo, InjectedProviderWithMeta, InjectedWindow, ProviderList, Unsubcall, Web3AccountsOptions } from '@polkadot/extension-inject/types'; - -import { u8aEq } from '@polkadot/util'; -import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; - -import { documentReadyPromise } from './util'; - -// expose utility functions -export { packageInfo } from './packageInfo'; -export { unwrapBytes, wrapBytes } from './wrapBytes'; - -// just a helper (otherwise we cast all-over, so shorter and more readable) -const win = window as Window & InjectedWindow; - -// don't clobber the existing object, but ensure non-undefined -win.injectedWeb3 = win.injectedWeb3 || {}; - -// true when anything has been injected and is available -function web3IsInjected (): boolean { - return Object.keys(win.injectedWeb3).length !== 0; -} - -// helper to throw a consistent error when not enabled -function throwError (method: string): never { - throw new Error(`${method}: web3Enable(originName) needs to be called before ${method}`); -} - -// internal helper to map from Array -> Array -function mapAccounts (source: string, list: InjectedAccount[], ss58Format?: number): InjectedAccountWithMeta[] { - return list.map(({ address, genesisHash, name, type }): InjectedAccountWithMeta => { - const encodedAddress = address.length === 42 ? address : encodeAddress(decodeAddress(address), ss58Format); - - return ({ - address: encodedAddress, - meta: { genesisHash, name, source }, - type - }); - }); -} - -// have we found a properly constructed window.injectedWeb3 -let isWeb3Injected = web3IsInjected(); - -// we keep the last promise created around (for queries) -let web3EnablePromise: Promise | null = null; - -export { isWeb3Injected, web3EnablePromise }; - -function getWindowExtensions (originName: string): Promise<[InjectedExtensionInfo, Injected | void][]> { - return Promise.all( - Object.entries(win.injectedWeb3).map( - ([name, { enable, version }]): Promise<[InjectedExtensionInfo, Injected | void]> => - Promise.all([ - Promise.resolve({ name, version }), - enable(originName).catch((error: Error): void => { - console.error(`Error initializing ${name}: ${error.message}`); - }) - ]) - ) - ); -} - -// enables all the providers found on the injected window interface -export function web3Enable (originName: string, compatInits: (() => Promise)[] = []): Promise { - if (!originName) { - throw new Error('You must pass a name for your app to the web3Enable function'); - } - - const initCompat = compatInits.length - ? Promise.all(compatInits.map((c) => c().catch(() => false))) - : Promise.resolve([true]); - - web3EnablePromise = documentReadyPromise( - (): Promise => - initCompat.then(() => - getWindowExtensions(originName) - .then((values): InjectedExtension[] => - values - .filter((value): value is [InjectedExtensionInfo, Injected] => !!value[1]) - .map(([info, ext]): InjectedExtension => { - // if we don't have an accounts subscriber, add a single-shot version - if (!ext.accounts.subscribe) { - ext.accounts.subscribe = (cb: (accounts: InjectedAccount[]) => void | Promise): Unsubcall => { - ext.accounts.get().then(cb).catch(console.error); - - return (): void => { - // no ubsubscribe needed, this is a single-shot - }; - }; - } - - return { ...info, ...ext }; - }) - ) - .catch((): InjectedExtension[] => []) - .then((values): InjectedExtension[] => { - const names = values.map(({ name, version }): string => `${name}/${version}`); - - isWeb3Injected = web3IsInjected(); - console.log(`web3Enable: Enabled ${values.length} extension${values.length !== 1 ? 's' : ''}: ${names.join(', ')}`); - - return values; - }) - ) - ); - - return web3EnablePromise; -} - -// retrieve all the accounts across all providers -export async function web3Accounts ({ accountType, ss58Format }: Web3AccountsOptions = {}): Promise { - if (!web3EnablePromise) { - return throwError('web3Accounts'); - } - - const accounts: InjectedAccountWithMeta[] = []; - const injected = await web3EnablePromise; - - const retrieved = await Promise.all( - injected.map(async ({ accounts, name: source }): Promise => { - try { - const list = await accounts.get(); - - return mapAccounts(source, list.filter(({ type }) => type && accountType ? accountType.includes(type) : true), ss58Format); - } catch (error) { - // cannot handle this one - return []; - } - }) - ); - - retrieved.forEach((result): void => { - accounts.push(...result); - }); - - const addresses = accounts.map(({ address }) => address); - - console.log(`web3Accounts: Found ${accounts.length} address${accounts.length !== 1 ? 'es' : ''}: ${addresses.join(', ')}`); - - return accounts; -} - -export async function web3AccountsSubscribe (cb: (accounts: InjectedAccountWithMeta[]) => void | Promise, { ss58Format }: Web3AccountsOptions = {}): Promise { - if (!web3EnablePromise) { - return throwError('web3AccountsSubscribe'); - } - - const accounts: Record = {}; - - const triggerUpdate = (): void | Promise => - cb( - Object.entries(accounts).reduce( - (result: InjectedAccountWithMeta[], [source, list]): InjectedAccountWithMeta[] => { - result.push(...mapAccounts(source, list, ss58Format)); - - return result; - }, - [] - ) - ); - - const unsubs = (await web3EnablePromise).map( - ({ accounts: { subscribe }, name: source }): Unsubcall => - subscribe((result): void => { - accounts[source] = result; - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - triggerUpdate(); - }) - ); - - return (): void => { - unsubs.forEach((unsub): void => { - unsub(); - }); - }; -} - -// find a specific provider based on the name -export async function web3FromSource (source: string): Promise { - if (!web3EnablePromise) { - return throwError('web3FromSource'); - } - - const sources = await web3EnablePromise; - const found = source && sources.find(({ name }): boolean => name === source); - - if (!found) { - throw new Error(`web3FromSource: Unable to find an injected ${source}`); - } - - return found; -} - -// find a specific provider based on an address -export async function web3FromAddress (address: string): Promise { - if (!web3EnablePromise) { - return throwError('web3FromAddress'); - } - - const accounts = await web3Accounts(); - let found: InjectedAccountWithMeta | undefined; - - if (address) { - const accountU8a = decodeAddress(address); - - found = accounts.find((account): boolean => u8aEq(decodeAddress(account.address), accountU8a)); - } - - if (!found) { - throw new Error(`web3FromAddress: Unable to find injected ${address}`); - } - - return web3FromSource(found.meta.source); -} - -// retrieve all providers exposed by one source -export async function web3ListRpcProviders (source: string): Promise { - const { provider } = await web3FromSource(source); - - if (!provider) { - console.warn(`Extension ${source} does not expose any provider`); - - return null; - } - - return provider.listProviders(); -} - -// retrieve all providers exposed by one source -export async function web3UseRpcProvider (source: string, key: string): Promise { - const { provider } = await web3FromSource(source); - - if (!provider) { - throw new Error(`Extension ${source} does not expose any provider`); - } - - const meta = await provider.startProvider(key); - - return { meta, provider }; -} +export * from './bundle'; diff --git a/packages/extension-inject/src/bundle.ts b/packages/extension-inject/src/bundle.ts new file mode 100644 index 00000000..953e5d28 --- /dev/null +++ b/packages/extension-inject/src/bundle.ts @@ -0,0 +1,24 @@ +// Copyright 2019-2021 @polkadot/extension-inject authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Injected, InjectedWindow, InjectOptions } from './types'; + +export { packageInfo } from './packageInfo'; + +// It is recommended to always use the function below to shield the extension and dapp from +// any future changes. The exposed interface will manage access between the 2 environments, +// be it via window (current), postMessage (under consideration) or any other mechanism +export function injectExtension (enable: (origin: string) => Promise, { name, version }: InjectOptions): void { + // small helper with the typescript types, just cast window + const windowInject = window as Window & InjectedWindow; + + // don't clobber the existing object, we will add it (or create as needed) + windowInject.injectedWeb3 = windowInject.injectedWeb3 || {}; + + // add our enable function + windowInject.injectedWeb3[name] = { + enable: (origin: string): Promise => + enable(origin), + version + }; +} diff --git a/packages/extension-inject/src/index.ts b/packages/extension-inject/src/index.ts index f5cf3f33..c374751e 100644 --- a/packages/extension-inject/src/index.ts +++ b/packages/extension-inject/src/index.ts @@ -3,24 +3,4 @@ import './detectPackage'; -import type { Injected, InjectedWindow, InjectOptions } from './types'; - -export { packageInfo } from './packageInfo'; - -// It is recommended to always use the function below to shield the extension and dapp from -// any future changes. The exposed interface will manage access between the 2 environments, -// be it via window (current), postMessage (under consideration) or any other mechanism -export function injectExtension (enable: (origin: string) => Promise, { name, version }: InjectOptions): void { - // small helper with the typescript types, just cast window - const windowInject = window as Window & InjectedWindow; - - // don't clobber the existing object, we will add it (or create as needed) - windowInject.injectedWeb3 = windowInject.injectedWeb3 || {}; - - // add our enable function - windowInject.injectedWeb3[name] = { - enable: (origin: string): Promise => - enable(origin), - version - }; -} +export * from './bundle';