Add bundle exports for packages with detect (#951)
This commit is contained in:
parent
ea06105474
commit
e71c11e72b
4
packages/extension-base/src/bundle.ts
Normal file
4
packages/extension-base/src/bundle.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// Copyright 2019-2021 @polkadot/extension-base authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { packageInfo } from './packageInfo';
|
||||
@ -3,4 +3,4 @@
|
||||
|
||||
import './detectPackage';
|
||||
|
||||
export { packageInfo } from './packageInfo';
|
||||
export * from './bundle';
|
||||
|
||||
87
packages/extension-chains/src/bundle.ts
Normal file
87
packages/extension-chains/src/bundle.ts
Normal file
@ -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 <url>`
|
||||
|
||||
const definitions = new Map<string, MetadataDef>(
|
||||
// [kusama].map((def) => [def.genesisHash, def])
|
||||
);
|
||||
|
||||
const expanded = new Map<string, Chain>();
|
||||
|
||||
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()];
|
||||
}
|
||||
@ -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 <url>`
|
||||
|
||||
const definitions = new Map<string, MetadataDef>(
|
||||
// [kusama].map((def) => [def.genesisHash, def])
|
||||
);
|
||||
|
||||
const expanded = new Map<string, Chain>();
|
||||
|
||||
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';
|
||||
|
||||
102
packages/extension-compat-metamask/src/bundle.ts
Normal file
102
packages/extension-compat-metamask/src/bundle.ts
Normal file
@ -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<unknown>;
|
||||
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<Injected> => {
|
||||
const providerRaw = await detectEthereumProvider({ mustBeMetaMask: true });
|
||||
const provider = isMetaMaskProvider(providerRaw);
|
||||
|
||||
await provider.request({ method: 'eth_requestAccounts' });
|
||||
|
||||
return {
|
||||
accounts: {
|
||||
get: async (): Promise<InjectedAccount[]> => {
|
||||
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<SignerResult> => {
|
||||
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<boolean> {
|
||||
return new Promise((resolve): void => {
|
||||
const win = window as Window & Web3Window;
|
||||
|
||||
if (win.ethereum) {
|
||||
injectMetaMaskWeb3(win);
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -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<unknown>;
|
||||
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<Injected> => {
|
||||
const providerRaw = await detectEthereumProvider({ mustBeMetaMask: true });
|
||||
const provider = isMetaMaskProvider(providerRaw);
|
||||
|
||||
await provider.request({ method: 'eth_requestAccounts' });
|
||||
|
||||
return {
|
||||
accounts: {
|
||||
get: async (): Promise<InjectedAccount[]> => {
|
||||
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<SignerResult> => {
|
||||
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<boolean> {
|
||||
return new Promise((resolve): void => {
|
||||
const win = window as Window & Web3Window;
|
||||
|
||||
if (win.ethereum) {
|
||||
injectMetaMaskWeb3(win);
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
export * from './bundle';
|
||||
|
||||
244
packages/extension-dapp/src/bundle.ts
Normal file
244
packages/extension-dapp/src/bundle.ts
Normal file
@ -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<InjectedAccount> -> Array<InjectedAccountWithMeta>
|
||||
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<InjectedExtension[]> | 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<boolean>)[] = []): Promise<InjectedExtension[]> {
|
||||
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<InjectedExtension[]> =>
|
||||
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<void>): 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<InjectedAccountWithMeta[]> {
|
||||
if (!web3EnablePromise) {
|
||||
return throwError('web3Accounts');
|
||||
}
|
||||
|
||||
const accounts: InjectedAccountWithMeta[] = [];
|
||||
const injected = await web3EnablePromise;
|
||||
|
||||
const retrieved = await Promise.all(
|
||||
injected.map(async ({ accounts, name: source }): Promise<InjectedAccountWithMeta[]> => {
|
||||
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<void>, { ss58Format }: Web3AccountsOptions = {}): Promise<Unsubcall> {
|
||||
if (!web3EnablePromise) {
|
||||
return throwError('web3AccountsSubscribe');
|
||||
}
|
||||
|
||||
const accounts: Record<string, InjectedAccount[]> = {};
|
||||
|
||||
const triggerUpdate = (): void | Promise<void> =>
|
||||
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<InjectedExtension> {
|
||||
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<InjectedExtension> {
|
||||
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<ProviderList | null> {
|
||||
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<InjectedProviderWithMeta> {
|
||||
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 };
|
||||
}
|
||||
@ -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<InjectedAccount> -> Array<InjectedAccountWithMeta>
|
||||
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<InjectedExtension[]> | 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<boolean>)[] = []): Promise<InjectedExtension[]> {
|
||||
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<InjectedExtension[]> =>
|
||||
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<void>): 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<InjectedAccountWithMeta[]> {
|
||||
if (!web3EnablePromise) {
|
||||
return throwError('web3Accounts');
|
||||
}
|
||||
|
||||
const accounts: InjectedAccountWithMeta[] = [];
|
||||
const injected = await web3EnablePromise;
|
||||
|
||||
const retrieved = await Promise.all(
|
||||
injected.map(async ({ accounts, name: source }): Promise<InjectedAccountWithMeta[]> => {
|
||||
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<void>, { ss58Format }: Web3AccountsOptions = {}): Promise<Unsubcall> {
|
||||
if (!web3EnablePromise) {
|
||||
return throwError('web3AccountsSubscribe');
|
||||
}
|
||||
|
||||
const accounts: Record<string, InjectedAccount[]> = {};
|
||||
|
||||
const triggerUpdate = (): void | Promise<void> =>
|
||||
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<InjectedExtension> {
|
||||
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<InjectedExtension> {
|
||||
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<ProviderList | null> {
|
||||
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<InjectedProviderWithMeta> {
|
||||
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';
|
||||
|
||||
24
packages/extension-inject/src/bundle.ts
Normal file
24
packages/extension-inject/src/bundle.ts
Normal file
@ -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<Injected>, { 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<Injected> =>
|
||||
enable(origin),
|
||||
version
|
||||
};
|
||||
}
|
||||
@ -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<Injected>, { 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<Injected> =>
|
||||
enable(origin),
|
||||
version
|
||||
};
|
||||
}
|
||||
export * from './bundle';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user