Explicit connect for light providers (#7230)

* Explicit connect for light providers

* Bump deps

* Adjust with explicit connect

* Remove old @substrate/connect

* Mock substrate-connect
This commit is contained in:
Jaco 2022-03-28 11:01:19 +02:00 committed by GitHub
parent ac75650d95
commit 1fb827ec34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 62 deletions

View File

@ -2,14 +2,17 @@
// SPDX-License-Identifier: Apache-2.0
import '@testing-library/jest-dom';
import './substrate-connect';
import { configure } from '@testing-library/dom';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line no-global-assign
CSS = { supports (): boolean {
return false;
} };
CSS = {
supports (): boolean {
return false;
}
}
configure({ asyncUtilTimeout: 10000 });

114
jest/substrate-connect.ts Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2017-2022 @polkadot/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
// duplicated from https://github.com/polkadot-js/api/blob/bb75b422730d6bf5189a87e9b7bbd1585bd5cd26/packages/rpc-provider/src/substrate-connect/ScProvider.spec.ts#L1
import type { Chain, JsonRpcCallback } from '@substrate/connect';
type MockChain = Chain & {
_spec: () => string
_recevedRequests: () => string[]
_isTerminated: () => boolean
_triggerCallback: (response: string | {}) => void
_setTerminateInterceptor: (fn: () => void) => void
_setSendJsonRpcInterceptor: (fn: (rpc: string) => void) => void
_getLatestRequest: () => string
}
const getFakeChain = (spec: string, callback: JsonRpcCallback): MockChain => {
const _receivedRequests: string[] = [];
let _isTerminated = false;
let terminateInterceptor: Function = Function.prototype;
let sendJsonRpcInterceptor: Function = Function.prototype;
return {
_spec: () => spec,
_recevedRequests: () => _receivedRequests,
_isTerminated: () => _isTerminated,
_triggerCallback: (response) => {
callback(
typeof response === 'string' ? response : JSON.stringify(response)
);
},
_setSendJsonRpcInterceptor: (fn) => {
sendJsonRpcInterceptor = fn;
},
_setTerminateInterceptor: (fn) => {
terminateInterceptor = fn;
},
sendJsonRpc: (rpc) => {
sendJsonRpcInterceptor(rpc);
_receivedRequests.push(rpc);
},
remove: () => {
terminateInterceptor();
_isTerminated = true;
},
_getLatestRequest: () => _receivedRequests[_receivedRequests.length - 1]
};
};
const getFakeClient = () => {
const chains: MockChain[] = [];
let addChainInterceptor: Promise<void> = Promise.resolve();
let addWellKnownChainInterceptor: Promise<void> = Promise.resolve();
return {
_chains: () => chains,
_setAddChainInterceptor: (interceptor: Promise<void>) => {
addChainInterceptor = interceptor;
},
_setAddWellKnownChainInterceptor: (interceptor: Promise<void>) => {
addWellKnownChainInterceptor = interceptor;
},
addChain: (chainSpec: string, cb: JsonRpcCallback): Promise<MockChain> =>
addChainInterceptor.then(() => {
const result = getFakeChain(chainSpec, cb);
chains.push(result);
return result;
}),
addWellKnownChain: (
wellKnownChain: string,
cb: JsonRpcCallback
): Promise<MockChain> =>
addWellKnownChainInterceptor.then(() => {
const result = getFakeChain(wellKnownChain, cb);
chains.push(result);
return result;
})
};
};
enum WellKnownChain {
polkadot = 'polkadot',
ksmcc3 = 'ksmcc3',
rococo_v2_1 = 'rococo_v2_1',
westend2 = 'westend2'
}
const connectorFactory = () => {
const clients: ReturnType<typeof getFakeClient>[] = [];
const latestClient = () => clients[clients.length - 1];
return {
WellKnownChain,
createScClient: () => {
const result = getFakeClient();
clients.push(result);
return result;
},
_clients: () => clients,
latestClient,
latestChain: () =>
latestClient()._chains()[latestClient()._chains().length - 1]
};
};
jest.mock('@substrate/connect', () => connectorFactory());

View File

@ -17,7 +17,7 @@
"@polkadot/api": "^7.14.2",
"@polkadot/extension-compat-metamask": "^0.42.10-4",
"@polkadot/extension-dapp": "^0.42.10-4",
"@substrate/connect": "^0.5.0",
"@polkadot/rpc-provider": "^7.14.2",
"fflate": "^0.7.3",
"rxjs": "^7.5.5"
}

View File

@ -1,15 +1,13 @@
// Copyright 2017-2022 @polkadot/react-api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SupportedChains } from '@substrate/connect';
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
import type { InjectedExtension } from '@polkadot/extension-inject/types';
import type { ChainProperties, ChainType } from '@polkadot/types/interfaces';
import type { KeyringStore } from '@polkadot/ui-keyring/types';
import type { ApiProps, ApiState } from './types';
import { ScProvider } from '@substrate/connect';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import store from 'store';
import { ApiPromise, WsProvider } from '@polkadot/api';
@ -20,6 +18,7 @@ import { TokenUnit } from '@polkadot/react-components/InputNumber';
import { StatusContext } from '@polkadot/react-components/Status';
import { useApiUrl, useEndpoint } from '@polkadot/react-hooks';
import ApiSigner from '@polkadot/react-signer/signers/ApiSigner';
import { ScProvider } from '@polkadot/rpc-provider/substrate-connect';
import { keyring } from '@polkadot/ui-keyring';
import { settings } from '@polkadot/ui-settings';
import { formatBalance, isNumber, isTestChain, objectSpread, stringify } from '@polkadot/util';
@ -184,6 +183,31 @@ async function loadOnReady (api: ApiPromise, endpoint: LinkOption | null, inject
};
}
// eslint-disable-next-line @typescript-eslint/require-await
async function createApi (apiUrl: string, signer: ApiSigner): Promise<Record<string, Record<string, string>>> {
const types = getDevTypes();
const isLight = apiUrl.startsWith('light://');
const provider = isLight
? new ScProvider(apiUrl.replace('light://substrate-connect/', '') as 'polkadot')
: new WsProvider(apiUrl);
api = new ApiPromise({
provider,
registry,
signer,
types,
typesBundle,
typesChain
});
// See https://github.com/polkadot-js/api/pull/4672#issuecomment-1078843960
if (isLight) {
await provider.connect();
}
return types;
}
function Api ({ apiUrl, children, isElectron, store }: Props): React.ReactElement<Props> | null {
const { queuePayload, queueSetTxStatus } = useContext(StatusContext);
const [state, setState] = useState<ApiState>({ hasInjectedAccounts: false, isApiReady: false } as unknown as ApiState);
@ -199,48 +223,43 @@ function Api ({ apiUrl, children, isElectron, store }: Props): React.ReactElemen
[apiEndpoint]
);
const apiRelay = useApiUrl(relayUrls);
const value = useMemo<ApiProps>(
() => objectSpread({}, state, { api, apiEndpoint, apiError, apiRelay, apiUrl, extensions, isApiConnected, isApiInitialized, isElectron, isWaitingInjected: !extensions }),
[apiError, extensions, isApiConnected, isApiInitialized, isElectron, state, apiEndpoint, apiRelay, apiUrl]
);
const onError = useCallback(
(error: unknown): void => {
console.error(error);
setApiError((error as Error).message);
},
[setApiError]
);
// initial initialization
useEffect((): void => {
const types = getDevTypes();
createApi(apiUrl, new ApiSigner(registry, queuePayload, queueSetTxStatus))
.then((types): void => {
api.on('connected', () => setIsApiConnected(true));
api.on('disconnected', () => setIsApiConnected(false));
api.on('error', onError);
api.on('ready', (): void => {
const injectedPromise = web3Enable('polkadot-js/apps');
api = new ApiPromise({
provider: apiUrl.startsWith('light://')
? new ScProvider(apiUrl.replace('light://substrate-connect/', '') as SupportedChains)
: new WsProvider(apiUrl),
registry,
signer: new ApiSigner(registry, queuePayload, queueSetTxStatus),
types,
typesBundle,
typesChain
});
injectedPromise
.then(setExtensions)
.catch(console.error);
api.on('connected', () => setIsApiConnected(true));
api.on('disconnected', () => setIsApiConnected(false));
api.on('error', (error: Error) => setApiError(error.message));
api.on('ready', (): void => {
const injectedPromise = web3Enable('polkadot-js/apps');
injectedPromise
.then(setExtensions)
.catch(console.error);
loadOnReady(api, apiEndpoint, injectedPromise, store, types)
.then(setState)
.catch((error): void => {
console.error(error);
setApiError((error as Error).message);
loadOnReady(api, apiEndpoint, injectedPromise, store, types)
.then(setState)
.catch(onError);
});
});
setIsApiInitialized(true);
}, [apiEndpoint, apiUrl, queuePayload, queueSetTxStatus, store]);
setIsApiInitialized(true);
})
.catch(onError);
}, [apiEndpoint, apiUrl, onError, queuePayload, queueSetTxStatus, store]);
if (!value.isApiInitialized) {
return null;

View File

@ -3267,7 +3267,7 @@ __metadata:
"@polkadot/api": ^7.14.2
"@polkadot/extension-compat-metamask": ^0.42.10-4
"@polkadot/extension-dapp": ^0.42.10-4
"@substrate/connect": ^0.5.0
"@polkadot/rpc-provider": ^7.14.2
fflate: ^0.7.3
rxjs: ^7.5.5
languageName: unknown
@ -4011,29 +4011,6 @@ __metadata:
languageName: node
linkType: hard
"@substrate/connect@npm:^0.5.0":
version: 0.5.0
resolution: "@substrate/connect@npm:0.5.0"
dependencies:
"@polkadot/rpc-provider": ^7.6.1
"@substrate/connect-extension-protocol": ^1.0.0
"@substrate/smoldot-light": 0.5.18
eventemitter3: ^4.0.7
checksum: c2d3d4b42e7859ed23aa2af7920c4ac9f97889e02bf0d71a8cf8ad5d38cd76ec64393fbe1b92470f8a2fafccbd1f8dabce776ea206b330e9e9c22b91c1fcd578
languageName: node
linkType: hard
"@substrate/smoldot-light@npm:0.5.18":
version: 0.5.18
resolution: "@substrate/smoldot-light@npm:0.5.18"
dependencies:
buffer: ^6.0.1
pako: ^2.0.4
websocket: ^1.0.32
checksum: 670dcfeb817aaa8098e8e303bec62d83c184b651ffb892f4b6c0698d51c2f3f1ff7240299590770b76891e89aeecaebaf12c2bfb92aa3d2b579aacae14dc062b
languageName: node
linkType: hard
"@substrate/smoldot-light@npm:0.6.8":
version: 0.6.8
resolution: "@substrate/smoldot-light@npm:0.6.8"