Expose subscribe on Accounts interface (#42)
* Expose subscribe on Accounts interface * Basic subscription for accounts on background * Subscriptions should be in-place... * Add web3AccountsSubscribe
This commit is contained in:
parent
43fc1314bf
commit
d7b0b6fcf5
15
README.md
15
README.md
@ -47,7 +47,7 @@ window.injectedWeb3 = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When there is more than one extension, each will populate an entry above, so from an extension implementation perspective, the structure should not be overridded. The injected interface, when returned via `enable`, contains the following -
|
When there is more than one extension, each will populate an entry above, so from an extension implementation perspective, the structure should not be overridded. The `Injected` interface, as returned via `enable`, contains the following -
|
||||||
|
|
||||||
```js
|
```js
|
||||||
interface Injected {
|
interface Injected {
|
||||||
@ -57,12 +57,17 @@ interface Injected {
|
|||||||
// readonly provider: Provider
|
// readonly provider: Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Account = {
|
||||||
|
readonly address: string; // ss-58 encoded address
|
||||||
|
readonly name?: string; // optional name for display
|
||||||
|
};
|
||||||
|
|
||||||
// exposes accounts
|
// exposes accounts
|
||||||
interface Accounts {
|
interface Accounts {
|
||||||
get (): Promise<Array<{
|
// retrieves the list of accounts for right now
|
||||||
readonly address: string; // ss-58 encoded address
|
get (): Promise<Array<Account>>;
|
||||||
readonly name?: string; // optional name for display
|
// subscribe to all accounts, updating as they change
|
||||||
}>>;
|
subscribe (cb: (accounts: Array<Account>) => any): () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// a signer that communicates with the extension via sendMessage
|
// a signer that communicates with the extension via sendMessage
|
||||||
|
|||||||
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
A basic extractor that manipulates the `window.injectedWeb3` to retrieve all the providers added to the page. It has a number of utilities -
|
A basic extractor that manipulates the `window.injectedWeb3` to retrieve all the providers added to the page. It has a number of utilities -
|
||||||
|
|
||||||
- `web3Enable(dappName: string)` - to be called before anything else, retrieves the list of all injected extensions/providers
|
- `web3Enable(dappName: string): Promise<Array<InjectedExtension>>` - to be called before anything else, retrieves the list of all injected extensions/providers
|
||||||
- `web3Accounts()` - returns a list of all the injected accounts, accross all extensions (source in meta)
|
- `web3Accounts(): Promise<Array<InjectedAccountWithMeta>>` - returns a list of all the injected accounts, accross all extensions (source in meta)
|
||||||
- `web3FromAddress(address: string)` - Retrieves a provider for a specific address
|
- `web3AccountsSubscribe(cb: (accounts: Array<InjectedAccountWithMeta>) => any): Promise<Unsubcall>` - subscribes to the accounts accross all extensions, returning a full list as changes are made
|
||||||
- `web3FromSource(name: string)` - Retriebes a provider identified by the name
|
- `web3FromAddress(address: string): Promise<InjectedExtension>` - Retrieves a provider for a specific address
|
||||||
- `isWeb3Injected` - boolean to indicate if `injectedWeb3` was found on the page
|
- `web3FromSource(name: string): Promise<InjectedExtension>` - Retrieves a provider identified by the name
|
||||||
- `web3EnablePromise` - `null` or the value of the last call to `web3Enable`
|
- `isWeb3Injected: boolean` - boolean to indicate if `injectedWeb3` was found on the page
|
||||||
|
- `web3EnablePromise: Promise<Array<InjectedExtension>> | null` - `null` or the promise as a result of the last call to `web3Enable`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import singleSource from './singleSource';
|
import singleSource from './singleSource';
|
||||||
|
|
||||||
|
// initialize all the compatibility engines
|
||||||
export default function initCompat (): Promise<boolean> {
|
export default function initCompat (): Promise<boolean> {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
singleSource()
|
singleSource()
|
||||||
|
|||||||
@ -5,9 +5,11 @@
|
|||||||
import { Signer } from '@polkadot/api/types';
|
import { Signer } from '@polkadot/api/types';
|
||||||
import { InjectedAccount, InjectedWindow } from '../types';
|
import { InjectedAccount, InjectedWindow } from '../types';
|
||||||
|
|
||||||
// RxJs interface, only what we need here
|
// RxJs interface, only the bare-bones of what we need here
|
||||||
type Subscriber<T> = {
|
type Subscriber<T> = {
|
||||||
subscribe: (cb: (value: Array<T>) => void) => void
|
subscribe: (cb: (value: T) => void) => {
|
||||||
|
unsubscribe (): any
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type SingleSourceAccount = {
|
type SingleSourceAccount = {
|
||||||
@ -17,7 +19,7 @@ type SingleSourceAccount = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type SingleSource = {
|
type SingleSource = {
|
||||||
accounts$: Subscriber<SingleSourceAccount>,
|
accounts$: Subscriber<Array<SingleSourceAccount>>,
|
||||||
environment$: Subscriber<string>,
|
environment$: Subscriber<string>,
|
||||||
signer: Signer
|
signer: Signer
|
||||||
};
|
};
|
||||||
@ -26,6 +28,14 @@ type SingleWindow = Window & InjectedWindow & {
|
|||||||
SingleSource: SingleSource
|
SingleSource: SingleSource
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// transfor the SingleSource accounts into a simple address/name array
|
||||||
|
function transformAccounts (accounts: Array<SingleSourceAccount>): Array<InjectedAccount> {
|
||||||
|
return accounts.map(({ address, name }) => ({
|
||||||
|
address,
|
||||||
|
name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// add a compat interface of SingleSource to window.injectedWeb3
|
// add a compat interface of SingleSource to window.injectedWeb3
|
||||||
function injectSingleSource (win: SingleWindow): void {
|
function injectSingleSource (win: SingleWindow): void {
|
||||||
let accounts: Array<InjectedAccount> = [];
|
let accounts: Array<InjectedAccount> = [];
|
||||||
@ -33,17 +43,24 @@ function injectSingleSource (win: SingleWindow): void {
|
|||||||
// we don't yet have an accounts subscribe on the interface, simply get the
|
// we don't yet have an accounts subscribe on the interface, simply get the
|
||||||
// accounts and store them, any get will resolve the last found values
|
// accounts and store them, any get will resolve the last found values
|
||||||
win.SingleSource.accounts$.subscribe((_accounts) => {
|
win.SingleSource.accounts$.subscribe((_accounts) => {
|
||||||
accounts = _accounts.map(({ address, name }) => ({
|
accounts = transformAccounts(_accounts);
|
||||||
address,
|
|
||||||
name
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// decorate the compat interface
|
// decorate the compat interface
|
||||||
win.injectedWeb3['SingleSource'] = {
|
win.injectedWeb3['SingleSource'] = {
|
||||||
enable: async (origin: string) => ({
|
enable: async (origin: string) => ({
|
||||||
accounts: {
|
accounts: {
|
||||||
get: async () => accounts
|
get: async () =>
|
||||||
|
accounts,
|
||||||
|
subscribe: (cb: (accounts: Array<InjectedAccount>) => any) => {
|
||||||
|
const sub = win.SingleSource.accounts$.subscribe((accounts) =>
|
||||||
|
cb(transformAccounts(accounts))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
sub.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
signer: win.SingleSource.signer
|
signer: win.SingleSource.signer
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
import { InjectedAccountWithMeta, InjectedExtension, InjectedExtensionInfo, InjectedWindow } from './types';
|
import { InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedExtensionInfo, InjectedWindow, Unsubcall } from './types';
|
||||||
|
|
||||||
// our extension adaptor for other kinds of extensions
|
// our extension adaptor for other kinds of extensions
|
||||||
import compatInjector from './compat';
|
import compatInjector from './compat';
|
||||||
@ -18,6 +18,19 @@ function web3IsInjected (): boolean {
|
|||||||
return Object.keys(win.injectedWeb3).length !== 0;
|
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: Array<InjectedAccount>): Array<InjectedAccountWithMeta> {
|
||||||
|
return list.map(({ address, name }) => ({
|
||||||
|
address,
|
||||||
|
meta: { name, source }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// have we found a properly constructed window.injectedWeb3
|
// have we found a properly constructed window.injectedWeb3
|
||||||
let isWeb3Injected = web3IsInjected();
|
let isWeb3Injected = web3IsInjected();
|
||||||
|
|
||||||
@ -56,7 +69,7 @@ export function web3Enable (originName: string): Promise<Array<InjectedExtension
|
|||||||
// retrieve all the accounts accross all providers
|
// retrieve all the accounts accross all providers
|
||||||
export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> {
|
export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> {
|
||||||
if (!web3EnablePromise) {
|
if (!web3EnablePromise) {
|
||||||
throw new Error(`web3Accounts: web3Enable(originName) needs to be called before web3Accounts`);
|
return throwError('web3Accounts');
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts: Array<InjectedAccountWithMeta> = [];
|
const accounts: Array<InjectedAccountWithMeta> = [];
|
||||||
@ -66,10 +79,7 @@ export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> {
|
|||||||
try {
|
try {
|
||||||
const list = await accounts.get();
|
const list = await accounts.get();
|
||||||
|
|
||||||
return list.map(({ address, name }) => ({
|
return mapAccounts(source, list);
|
||||||
address,
|
|
||||||
meta: { name, source }
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// cannot handle this one
|
// cannot handle this one
|
||||||
return [];
|
return [];
|
||||||
@ -86,10 +96,36 @@ export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> {
|
|||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function web3AccountsSubscribe (cb: (accounts: Array<InjectedAccountWithMeta>) => any): Promise<Unsubcall> {
|
||||||
|
if (!web3EnablePromise) {
|
||||||
|
return throwError('web3AccountsSubscribe');
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts: { [source: string]: Array<InjectedAccount> } = {};
|
||||||
|
const triggerUpdate = (): void => {
|
||||||
|
cb(Object.entries(accounts).reduce((result, [source, list]) => {
|
||||||
|
result.push(...mapAccounts(source, list));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [] as Array<InjectedAccountWithMeta>));
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubs = (await web3EnablePromise).map(({ accounts: { subscribe }, name: source }) =>
|
||||||
|
subscribe((result) => {
|
||||||
|
accounts[source] = result;
|
||||||
|
triggerUpdate();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
unsubs.forEach((unsub) => unsub());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// find a specific provider based on an address
|
// find a specific provider based on an address
|
||||||
export async function web3FromAddress (address: string): Promise<InjectedExtension> {
|
export async function web3FromAddress (address: string): Promise<InjectedExtension> {
|
||||||
if (!web3EnablePromise) {
|
if (!web3EnablePromise) {
|
||||||
throw new Error(`web3FromAddress: web3Enable(originName) needs to be called before web3FromAddress`);
|
return throwError('web3FromAddress');
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = await web3Accounts();
|
const accounts = await web3Accounts();
|
||||||
@ -105,7 +141,7 @@ export async function web3FromAddress (address: string): Promise<InjectedExtensi
|
|||||||
// find a specific provider based on the name
|
// find a specific provider based on the name
|
||||||
export async function web3FromSource (source: string): Promise<InjectedExtension> {
|
export async function web3FromSource (source: string): Promise<InjectedExtension> {
|
||||||
if (!web3EnablePromise) {
|
if (!web3EnablePromise) {
|
||||||
throw new Error(`web3FromSource: web3Enable(originName) needs to be called before web3FromSource`);
|
return throwError('web3FromSource');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sources = await web3EnablePromise;
|
const sources = await web3EnablePromise;
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
import { Signer } from '@polkadot/api/types';
|
import { Signer } from '@polkadot/api/types';
|
||||||
|
|
||||||
|
export type Unsubcall = () => void;
|
||||||
|
|
||||||
export interface InjectedAccount {
|
export interface InjectedAccount {
|
||||||
address: string;
|
address: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -19,6 +21,7 @@ export interface InjectedAccountWithMeta {
|
|||||||
|
|
||||||
export interface InjectedAccounts {
|
export interface InjectedAccounts {
|
||||||
get: () => Promise<Array<InjectedAccount>>;
|
get: () => Promise<Array<InjectedAccount>>;
|
||||||
|
subscribe: (cb: (accounts: Array<InjectedAccount>) => any) => Unsubcall;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InjectedSigner extends Signer {}
|
export interface InjectedSigner extends Signer {}
|
||||||
|
|||||||
@ -2,14 +2,24 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Unsubcall } from '@polkadot/extension-dapp/types';
|
||||||
|
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||||
import { MessageTypes, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSign$Response } from '../types';
|
import { MessageTypes, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSign$Response } from '../types';
|
||||||
|
|
||||||
import keyring from '@polkadot/ui-keyring';
|
import keyring from '@polkadot/ui-keyring';
|
||||||
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||||
import { assert } from '@polkadot/util';
|
import { assert, isFunction } from '@polkadot/util';
|
||||||
|
|
||||||
import State from './State';
|
import State from './State';
|
||||||
|
|
||||||
|
type Accounts = Array<{ address: string, name?: string }>;
|
||||||
|
|
||||||
|
function transformAccounts (accounts: SubjectInfo): Accounts {
|
||||||
|
return Object.values(accounts).map(({ json: { address, meta: { name } } }) => ({
|
||||||
|
address, name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export default class Tabs {
|
export default class Tabs {
|
||||||
private _state: State;
|
private _state: State;
|
||||||
|
|
||||||
@ -21,12 +31,22 @@ export default class Tabs {
|
|||||||
return this._state.authorizeUrl(url, request);
|
return this._state.authorizeUrl(url, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private accountsList (url: string): Array<{ address: string, name?: string }> {
|
private accountsList (url: string): Accounts {
|
||||||
return Object
|
return transformAccounts(accountsObservable.subject.getValue());
|
||||||
.values(accountsObservable.subject.getValue())
|
}
|
||||||
.map(({ json: { address, meta: { name } } }) => ({
|
|
||||||
address, name
|
private accountsSubscribe (url: string, cb?: (accounts: Accounts) => void): Unsubcall {
|
||||||
}));
|
if (!isFunction(cb)) {
|
||||||
|
throw new Error('Expected accountsSubscribe to be passed a subscriber');
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo) =>
|
||||||
|
cb(transformAccounts(accounts))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private extrinsicSign (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSign$Response> {
|
private extrinsicSign (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSign$Response> {
|
||||||
@ -38,7 +58,7 @@ export default class Tabs {
|
|||||||
return this._state.signQueue(url, request);
|
return this._state.signQueue(url, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle (type: MessageTypes, request: any, url: string = 'unknown'): Promise<any> {
|
async handle (type: MessageTypes, request: any, url: string, subscriber?: (data: any) => void): Promise<any> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'authorize.tab':
|
case 'authorize.tab':
|
||||||
return this.authorize(url, request);
|
return this.authorize(url, request);
|
||||||
@ -46,6 +66,9 @@ export default class Tabs {
|
|||||||
case 'accounts.list':
|
case 'accounts.list':
|
||||||
return this.accountsList(url);
|
return this.accountsList(url);
|
||||||
|
|
||||||
|
case 'accounts.subscribe':
|
||||||
|
return this.accountsSubscribe(url, subscriber);
|
||||||
|
|
||||||
case 'extrinsic.sign':
|
case 'extrinsic.sign':
|
||||||
return this.extrinsicSign(url, request);
|
return this.extrinsicSign(url, request);
|
||||||
|
|
||||||
|
|||||||
@ -9,38 +9,54 @@ import Extension from './Extension';
|
|||||||
import State from './State';
|
import State from './State';
|
||||||
import Tabs from './Tabs';
|
import Tabs from './Tabs';
|
||||||
|
|
||||||
|
const FALLBACK_URL = '<unknown>';
|
||||||
|
|
||||||
const state = new State();
|
const state = new State();
|
||||||
const extension = new Extension(state);
|
const extension = new Extension(state);
|
||||||
const tabs = new Tabs(state);
|
const tabs = new Tabs(state);
|
||||||
|
|
||||||
export default function handler ({ id, message, request }: MessageRequest, port: chrome.runtime.Port): boolean {
|
function createSubscription (id: number, port: chrome.runtime.Port): (data: any) => void {
|
||||||
|
// FIXME We are not handling actual unsubscribes yet, this is an issue
|
||||||
|
return (subscription: any) => {
|
||||||
|
port.postMessage({ id, subscription });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler ({ id, message, request }: MessageRequest, port: chrome.runtime.Port): void {
|
||||||
const isPopup = port.name === PORT_POPUP;
|
const isPopup = port.name === PORT_POPUP;
|
||||||
const sender = port.sender as chrome.runtime.MessageSender;
|
const sender = port.sender as chrome.runtime.MessageSender;
|
||||||
const from = isPopup
|
const from = isPopup
|
||||||
? 'popup'
|
? 'popup'
|
||||||
: sender.tab
|
: sender.tab && sender.tab.url;
|
||||||
? sender.tab.url
|
const source = `${from || FALLBACK_URL}: ${id}: ${message}`;
|
||||||
: '<unknown>';
|
|
||||||
const source = `${from}: ${id}: ${message}`;
|
|
||||||
|
|
||||||
console.log(` [in] ${source}`); // :: ${JSON.stringify(request)}`);
|
console.log(` [in] ${source}`); // :: ${JSON.stringify(request)}`);
|
||||||
|
|
||||||
|
// This is not great - basically, based on the name (since there is only 1 atm),
|
||||||
|
// we create a subscription handler. Basically these handlers will continute stream
|
||||||
|
// results as they become available
|
||||||
|
const subscription = message.indexOf('.subscribe') !== -1
|
||||||
|
? createSubscription(id, port)
|
||||||
|
: undefined;
|
||||||
const promise = isPopup
|
const promise = isPopup
|
||||||
? extension.handle(message, request)
|
? extension.handle(message, request)
|
||||||
: tabs.handle(message, request, from);
|
: tabs.handle(message, request, from || FALLBACK_URL, subscription);
|
||||||
|
|
||||||
promise
|
promise
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`);
|
console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`);
|
||||||
|
|
||||||
port.postMessage({ id, response });
|
if (subscription) {
|
||||||
|
// TODO See unsub handling above, here we need to store the
|
||||||
|
// actual unsubscribe and handle appropriately
|
||||||
|
port.postMessage({ id, response: true });
|
||||||
|
} else {
|
||||||
|
port.postMessage({ id, response });
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(`[err] ${source}:: ${error.message}`);
|
console.log(`[err] ${source}:: ${error.message}`);
|
||||||
|
|
||||||
port.postMessage({ id, error: error.message });
|
port.postMessage({ id, error: error.message });
|
||||||
});
|
});
|
||||||
|
|
||||||
// return true to indicate we are sending the response async
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import { KeypairType } from '@polkadot/util-crypto/types';
|
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
|
|
||||||
export type MessageTypes = 'authorize.approve' | 'authorize.reject' | 'authorize.requests' | 'authorize.tab' | 'accounts.create' | 'accounts.edit' | 'accounts.forget' | 'accounts.list' | 'extrinsic.sign' | 'seed.create' | 'seed.validate' | 'signing.approve' | 'signing.cancel' | 'signing.requests';
|
export type MessageTypes = 'authorize.approve' | 'authorize.reject' | 'authorize.requests' | 'authorize.tab' | 'accounts.create' | 'accounts.edit' | 'accounts.forget' | 'accounts.list' | 'accounts.subscribe' | 'extrinsic.sign' | 'seed.create' | 'seed.validate' | 'signing.approve' | 'signing.cancel' | 'signing.requests';
|
||||||
|
|
||||||
export type AuthorizeRequest = [number, MessageAuthorize, string];
|
export type AuthorizeRequest = [number, MessageAuthorize, string];
|
||||||
|
|
||||||
@ -31,7 +31,8 @@ export type MessageRequest = {
|
|||||||
export type MessageResponse = {
|
export type MessageResponse = {
|
||||||
error?: string,
|
error?: string,
|
||||||
id: number,
|
id: number,
|
||||||
response?: any
|
response?: any,
|
||||||
|
subscription?: any
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageAccountCreate = {
|
export type MessageAccountCreate = {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
import { InjectedAccounts, InjectedAccount } from '@polkadot/extension-dapp/types';
|
import { InjectedAccounts, InjectedAccount, Unsubcall } from '@polkadot/extension-dapp/types';
|
||||||
import { SendRequest } from './types';
|
import { SendRequest } from './types';
|
||||||
|
|
||||||
let sendRequest: SendRequest;
|
let sendRequest: SendRequest;
|
||||||
@ -15,4 +15,13 @@ export default class Accounts implements InjectedAccounts {
|
|||||||
get (): Promise<Array<InjectedAccount>> {
|
get (): Promise<Array<InjectedAccount>> {
|
||||||
return sendRequest('accounts.list');
|
return sendRequest('accounts.list');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribe (cb: (accounts: Array<InjectedAccount>) => any): Unsubcall {
|
||||||
|
sendRequest('accounts.subscribe', null, cb)
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// FIXME we need the ability to unsubscribe
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,17 +7,18 @@ import { MessageTypes } from '../background/types';
|
|||||||
|
|
||||||
import Injected from './Injected';
|
import Injected from './Injected';
|
||||||
|
|
||||||
// when sending a message from the injector to the expension, we
|
// when sending a message from the injector to the extension, we
|
||||||
// - create an event - this we send to the loader
|
// - create an event - this we send to the loader
|
||||||
// - the loader takes this evend and uses sendMessage to the extension background
|
// - the loader takes this event and uses port.postMessage to background
|
||||||
// - on respoinse, the loader creates a reponse event
|
// - on resposnse, the loader creates a reponse event
|
||||||
// - this injector, reads listends on the events, maps it to the original
|
// - this injector, listens on the events, maps it to the original
|
||||||
// - resolves/rejects the promise with the result
|
// - resolves/rejects the promise with the result (or sub data)
|
||||||
|
|
||||||
type Handlers = {
|
type Handlers = {
|
||||||
[index: number]: {
|
[index: number]: {
|
||||||
resolve: (data: any) => void,
|
resolve: (data: any) => void,
|
||||||
reject: (error: Error) => void
|
reject: (error: Error) => void,
|
||||||
|
subscriber?: (data: any) => void
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,11 +29,11 @@ let idCounter = 0;
|
|||||||
|
|
||||||
// a generic message sender that creates an event, returning a promise that will
|
// a generic message sender that creates an event, returning a promise that will
|
||||||
// resolve once the event is resolved (by the response listener just below this)
|
// resolve once the event is resolved (by the response listener just below this)
|
||||||
function sendMessage (message: MessageTypes, request: any = null): Promise<any> {
|
function sendMessage (message: MessageTypes, request: any = null, subscriber?: (data: any) => void): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const id = ++idCounter;
|
const id = ++idCounter;
|
||||||
|
|
||||||
handlers[id] = { resolve, reject };
|
handlers[id] = { resolve, reject, subscriber };
|
||||||
|
|
||||||
window.postMessage({ id, message, origin: 'page', request }, '*');
|
window.postMessage({ id, message, origin: 'page', request }, '*');
|
||||||
});
|
});
|
||||||
@ -59,7 +60,12 @@ window.addEventListener('message', ({ data, source }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete handlers[data.id];
|
if (!handler.subscriber) {
|
||||||
|
delete handlers[data.id];
|
||||||
|
} else if (data.subscription) {
|
||||||
|
handler.subscriber(data.subscription);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
handler.reject(new Error(data.error));
|
handler.reject(new Error(data.error));
|
||||||
|
|||||||
@ -5,5 +5,5 @@
|
|||||||
import { MessageTypes } from '../background/types';
|
import { MessageTypes } from '../background/types';
|
||||||
|
|
||||||
export interface SendRequest {
|
export interface SendRequest {
|
||||||
(message: MessageTypes, request?: any): Promise<any>;
|
(message: MessageTypes, request?: any, subscriber?: (data: any) => any): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user