diff --git a/jest/jest-setup.ts b/jest/jest-setup.ts index 0e7a00df97..71fc9a84b3 100644 --- a/jest/jest-setup.ts +++ b/jest/jest-setup.ts @@ -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 }); diff --git a/jest/substrate-connect.ts b/jest/substrate-connect.ts new file mode 100644 index 0000000000..7fd8fe92b3 --- /dev/null +++ b/jest/substrate-connect.ts @@ -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 = Promise.resolve(); + let addWellKnownChainInterceptor: Promise = Promise.resolve(); + + return { + _chains: () => chains, + _setAddChainInterceptor: (interceptor: Promise) => { + addChainInterceptor = interceptor; + }, + _setAddWellKnownChainInterceptor: (interceptor: Promise) => { + addWellKnownChainInterceptor = interceptor; + }, + addChain: (chainSpec: string, cb: JsonRpcCallback): Promise => + addChainInterceptor.then(() => { + const result = getFakeChain(chainSpec, cb); + + chains.push(result); + + return result; + }), + addWellKnownChain: ( + wellKnownChain: string, + cb: JsonRpcCallback + ): Promise => + 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[] = []; + 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()); diff --git a/packages/react-api/package.json b/packages/react-api/package.json index 44af2e0cc2..dc22e42d48 100644 --- a/packages/react-api/package.json +++ b/packages/react-api/package.json @@ -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" } diff --git a/packages/react-api/src/Api.tsx b/packages/react-api/src/Api.tsx index ba0e0f2646..86b5cd4683 100644 --- a/packages/react-api/src/Api.tsx +++ b/packages/react-api/src/Api.tsx @@ -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>> { + 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 | null { const { queuePayload, queueSetTxStatus } = useContext(StatusContext); const [state, setState] = useState({ 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( () => 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; diff --git a/yarn.lock b/yarn.lock index 5ca428eaf9..9115cc1b8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"