connect(<dappName>) new-style support (#1175)
* connect(<dappName>) new-style support * Add connect interface * Seed required * Support both old & new codepaths in inject * Explicit enable(...) reject on new-style * Cleanups * Condense enable(...) error * Adjust enable ordering * Adjust web3IsInjected
This commit is contained in:
parent
230bd01f1c
commit
45a02f7268
@ -38,7 +38,7 @@ export default class Signer implements SignerInterface {
|
||||
}
|
||||
|
||||
// NOTE We don't listen to updates at all, if we do we can interpret the
|
||||
// resuklt as provided by the API here
|
||||
// result as provided by the API here
|
||||
// public update (id: number, status: Hash | SubmittableResult): void {
|
||||
// // ignore
|
||||
// }
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
// Copyright 2019-2022 @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 type { InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedProviderWithMeta, InjectedWindow, ProviderList, Unsubcall, Web3AccountsOptions } from '@polkadot/extension-inject/types';
|
||||
|
||||
import { u8aEq } from '@polkadot/util';
|
||||
import { objectSpread, u8aEq } from '@polkadot/util';
|
||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import { documentReadyPromise } from './util';
|
||||
@ -20,7 +20,10 @@ win.injectedWeb3 = win.injectedWeb3 || {};
|
||||
|
||||
// true when anything has been injected and is available
|
||||
function web3IsInjected (): boolean {
|
||||
return Object.keys(win.injectedWeb3).length !== 0;
|
||||
return Object
|
||||
.values(win.injectedWeb3)
|
||||
.filter(({ connect, enable }) => !!(connect || enable))
|
||||
.length !== 0;
|
||||
}
|
||||
|
||||
// helper to throw a consistent error when not enabled
|
||||
@ -47,18 +50,31 @@ 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}`);
|
||||
})
|
||||
])
|
||||
function getWindowExtensions (originName: string): Promise<InjectedExtension[]> {
|
||||
return Promise
|
||||
.all(
|
||||
Object
|
||||
.entries(win.injectedWeb3)
|
||||
.map(([nameOrHash, { connect, enable, version }]): Promise<(InjectedExtension | void)> =>
|
||||
Promise
|
||||
.resolve()
|
||||
.then(() =>
|
||||
connect
|
||||
// new style, returning all info
|
||||
? connect(originName)
|
||||
: enable
|
||||
// previous interface, leakages on name/version
|
||||
? enable(originName).then((e) =>
|
||||
objectSpread<InjectedExtension>({ name: nameOrHash, version: version || 'unknown' }, e)
|
||||
)
|
||||
: Promise.reject(new Error('No connect(..) or enable(...) hook found'))
|
||||
)
|
||||
.catch(({ message }: Error): void => {
|
||||
console.error(`Error initializing ${nameOrHash}: ${message}`);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
.then((exts) => exts.filter((e): e is InjectedExtension => !!e));
|
||||
}
|
||||
|
||||
// enables all the providers found on the injected window interface
|
||||
@ -76,29 +92,30 @@ export function web3Enable (originName: string, compatInits: (() => Promise<bool
|
||||
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);
|
||||
values.map((e): InjectedExtension => {
|
||||
// if we don't have an accounts subscriber, add a single-shot version
|
||||
if (!e.accounts.subscribe) {
|
||||
e.accounts.subscribe = (cb: (accounts: InjectedAccount[]) => void | Promise<void>): Unsubcall => {
|
||||
e.accounts
|
||||
.get()
|
||||
.then(cb)
|
||||
.catch(console.error);
|
||||
|
||||
return (): void => {
|
||||
// no ubsubscribe needed, this is a single-shot
|
||||
};
|
||||
return (): void => {
|
||||
// no ubsubscribe needed, this is a single-shot
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { ...info, ...ext };
|
||||
})
|
||||
return e;
|
||||
})
|
||||
)
|
||||
.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(', ')}`);
|
||||
console.info(`web3Enable: Enabled ${values.length} extension${values.length !== 1 ? 's' : ''}: ${names.join(', ')}`);
|
||||
|
||||
return values;
|
||||
})
|
||||
@ -118,27 +135,25 @@ export async function web3Accounts ({ accountType, extensions, ss58Format }: Web
|
||||
const injected = await web3EnablePromise;
|
||||
|
||||
const retrieved = await Promise.all(
|
||||
injected.filter(
|
||||
({ name: source }) => !extensions || extensions.includes(source)
|
||||
).map(async ({ accounts, name: source }): Promise<InjectedAccountWithMeta[]> => {
|
||||
try {
|
||||
const list = await accounts.get();
|
||||
injected
|
||||
.filter(({ name: source }) => !extensions || extensions.includes(source))
|
||||
.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 [];
|
||||
}
|
||||
})
|
||||
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(', ')}`);
|
||||
console.info(`web3Accounts: Found ${accounts.length} address${accounts.length !== 1 ? 'es' : ''}`);
|
||||
|
||||
return accounts;
|
||||
}
|
||||
@ -152,20 +167,18 @@ export async function web3AccountsSubscribe (cb: (accounts: InjectedAccountWithM
|
||||
|
||||
const triggerUpdate = (): void | Promise<void> =>
|
||||
cb(
|
||||
Object.entries(accounts).reduce(
|
||||
(result: InjectedAccountWithMeta[], [source, list]): InjectedAccountWithMeta[] => {
|
||||
Object
|
||||
.entries(accounts)
|
||||
.reduce((result: InjectedAccountWithMeta[], [source, list]): InjectedAccountWithMeta[] => {
|
||||
result.push(...mapAccounts(source, list, ss58Format));
|
||||
|
||||
return result;
|
||||
},
|
||||
[]
|
||||
)
|
||||
}, [])
|
||||
);
|
||||
|
||||
const unsubs = (await web3EnablePromise).filter(
|
||||
({ name: source }) => !extensions || extensions.includes(source)
|
||||
).map(
|
||||
({ accounts: { subscribe }, name: source }): Unsubcall =>
|
||||
const unsubs = (await web3EnablePromise)
|
||||
.filter(({ name: source }) => !extensions || extensions.includes(source))
|
||||
.map(({ accounts: { subscribe }, name: source }): Unsubcall =>
|
||||
subscribe((result): void => {
|
||||
accounts[source] = result;
|
||||
|
||||
@ -175,7 +188,7 @@ export async function web3AccountsSubscribe (cb: (accounts: InjectedAccountWithM
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
);
|
||||
|
||||
return (): void => {
|
||||
unsubs.forEach((unsub): void => {
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
// Copyright 2019-2022 @polkadot/extension-inject authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Injected, InjectedWindow, InjectOptions } from './types';
|
||||
import type { Injected, InjectedExtension, InjectedWindow, InjectOptions } from './types';
|
||||
|
||||
import { cyrb53 } from './cyrb53';
|
||||
|
||||
export { packageInfo } from './packageInfo';
|
||||
|
||||
// setting for new-style connect (more-secure with no details exposed without
|
||||
// user acknowledgement, however slightly less-compatible with all dapps, some
|
||||
// may have not upgraded and don't have access to the latest interfaces)
|
||||
//
|
||||
// NOTE: In future versions this will be made the default
|
||||
const IS_CONNECT_CAPABLE = false;
|
||||
|
||||
// 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
|
||||
@ -15,10 +24,24 @@ export function injectExtension (enable: (origin: string) => Promise<Injected>,
|
||||
// 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
|
||||
};
|
||||
if (IS_CONNECT_CAPABLE) {
|
||||
// expose our extension on the window object, new-style with connect(origin)
|
||||
windowInject.injectedWeb3[cyrb53(`${name}/${version}`)] = {
|
||||
connect: (origin: string): Promise<InjectedExtension> =>
|
||||
enable(origin).then(({ accounts, metadata, provider, signer }) => ({
|
||||
accounts, metadata, name, provider, signer, version
|
||||
})),
|
||||
enable: (): Promise<Injected> =>
|
||||
Promise.reject(
|
||||
new Error('This extension does not have support for enable(...), rather is only supports the new connect(...) variant (no extension name/version metadata without specific user-approval)')
|
||||
)
|
||||
};
|
||||
} else {
|
||||
// expose our extension on the window object, old-style with enable(origin)
|
||||
windowInject.injectedWeb3[name] = {
|
||||
enable: (origin: string): Promise<Injected> =>
|
||||
enable(origin),
|
||||
version
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
33
packages/extension-inject/src/cyrb53.ts
Normal file
33
packages/extension-inject/src/cyrb53.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2019-2022 @polkadot/extension-inject authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// cyrb53 (c) 2018 bryc (github.com/bryc)
|
||||
// A fast and simple hash function with decent collision resistance.
|
||||
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
||||
// Public domain. Attribution appreciated.
|
||||
//
|
||||
// From https://github.com/bryc/code/blob/fed42df9db547493452e32375c93d7854383e480/jshash/experimental/cyrb53.js
|
||||
// As shared in https://stackoverflow.com/a/52171480
|
||||
//
|
||||
// Small changes made to the code as linked above:
|
||||
// . - Seed value is required (set as Date.now() in usage, could change)
|
||||
// - Return value is a hex string (as per comment in SO answer)
|
||||
// - TS typings added
|
||||
// - Non-intrusive coding-style variable declaration changes
|
||||
export function cyrb53 (input: string, seed = Date.now()): string {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const ch = input.charCodeAt(i);
|
||||
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
// https://stackoverflow.com/a/52171480
|
||||
return (h2 >>> 0).toString(16).padStart(8, '0') + (h1 >>> 0).toString(16).padStart(8, '0');
|
||||
}
|
||||
@ -99,8 +99,9 @@ export interface Injected {
|
||||
}
|
||||
|
||||
export interface InjectedWindowProvider {
|
||||
enable: (origin: string) => Promise<Injected>;
|
||||
version: string;
|
||||
connect?: (origin: string) => Promise<InjectedExtension>;
|
||||
enable?: (origin: string) => Promise<Injected>;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface InjectedWindow extends This {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user