Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Explicit connect for light providers (polkadot-js#7230)
Browse files Browse the repository at this point in the history
* Explicit connect for light providers

* Bump deps

* Adjust with explicit connect

* Remove old @substrate/connect

* Mock substrate-connect
  • Loading branch information
jacogr authored Mar 28, 2022
1 parent ac75650 commit 1fb827e
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 64 deletions.
9 changes: 6 additions & 3 deletions jest/jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 114 additions & 0 deletions jest/substrate-connect.ts
Original file line number Diff line number Diff line change
@@ -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());
2 changes: 1 addition & 1 deletion packages/react-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
91 changes: 55 additions & 36 deletions packages/react-api/src/Api.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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);
Expand All @@ -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();

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
});

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);
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');

injectedPromise
.then(setExtensions)
.catch(console.error);

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;
Expand Down
25 changes: 1 addition & 24 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 1fb827e

Please sign in to comment.