PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@polkadot/rpc-provider/substrate-connect
Просмотр файла: index.js
import { EventEmitter } from 'eventemitter3';
import { isError, isFunction, isObject, logger, noop, objectSpread } from '@polkadot/util';
import { RpcCoder } from '../coder/index.js';
import { healthChecker } from './Health.js';
const l = logger('api-substrate-connect');
const subscriptionUnsubscriptionMethods = new Map([
['author_submitAndWatchExtrinsic', 'author_unwatchExtrinsic'],
['chain_subscribeAllHeads', 'chain_unsubscribeAllHeads'],
['chain_subscribeFinalizedHeads', 'chain_unsubscribeFinalizedHeads'],
['chain_subscribeFinalisedHeads', 'chain_subscribeFinalisedHeads'],
['chain_subscribeNewHeads', 'chain_unsubscribeNewHeads'],
['chain_subscribeNewHead', 'chain_unsubscribeNewHead'],
['chain_subscribeRuntimeVersion', 'chain_unsubscribeRuntimeVersion'],
['subscribe_newHead', 'unsubscribe_newHead'],
['state_subscribeRuntimeVersion', 'state_unsubscribeRuntimeVersion'],
['state_subscribeStorage', 'state_unsubscribeStorage']
]);
const scClients = new WeakMap();
export class ScProvider {
__internal__Sc;
__internal__coder = new RpcCoder();
__internal__spec;
__internal__sharedSandbox;
__internal__subscriptions = new Map();
__internal__resubscribeMethods = new Map();
__internal__requests = new Map();
__internal__wellKnownChains;
__internal__eventemitter = new EventEmitter();
__internal__chain = null;
__internal__isChainReady = false;
constructor(Sc, spec, sharedSandbox) {
if (!isObject(Sc) || !isObject(Sc.WellKnownChain) || !isFunction(Sc.createScClient)) {
throw new Error('Expected an @substrate/connect interface as first parameter to ScProvider');
}
this.__internal__Sc = Sc;
this.__internal__spec = spec;
this.__internal__sharedSandbox = sharedSandbox;
this.__internal__wellKnownChains = new Set(Object.values(Sc.WellKnownChain));
}
get hasSubscriptions() {
// Indicates that subscriptions are supported
return !!true;
}
get isClonable() {
return !!false;
}
get isConnected() {
return !!this.__internal__chain && this.__internal__isChainReady;
}
clone() {
throw new Error('clone() is not supported.');
}
// Config details can be found in @substrate/connect repo following the link:
// https://github.com/paritytech/substrate-connect/blob/main/packages/connect/src/connector/index.ts
async connect(config, checkerFactory = healthChecker) {
if (this.isConnected) {
throw new Error('Already connected!');
}
// it could happen that after emitting `disconnected` due to the fact that
// smoldot is syncing, the consumer tries to reconnect after a certain amount
// of time... In which case we want to make sure that we don't create a new
// chain.
if (this.__internal__chain) {
await this.__internal__chain;
return;
}
if (this.__internal__sharedSandbox && !this.__internal__sharedSandbox.isConnected) {
await this.__internal__sharedSandbox.connect();
}
const client = this.__internal__sharedSandbox
? scClients.get(this.__internal__sharedSandbox)
: this.__internal__Sc.createScClient(config);
if (!client) {
throw new Error('Unknown ScProvider!');
}
scClients.set(this, client);
const hc = checkerFactory();
const onResponse = (res) => {
const hcRes = hc.responsePassThrough(res);
if (!hcRes) {
return;
}
const response = JSON.parse(hcRes);
let decodedResponse;
try {
decodedResponse = this.__internal__coder.decodeResponse(response);
}
catch (e) {
decodedResponse = e;
}
// It's not a subscription message, but rather a standar RPC response
if (response.params?.subscription === undefined || !response.method) {
return this.__internal__requests.get(response.id)?.(decodedResponse);
}
// We are dealing with a subscription message
const subscriptionId = `${response.method}::${response.params.subscription}`;
const callback = this.__internal__subscriptions.get(subscriptionId)?.[0];
callback?.(decodedResponse);
};
const addChain = this.__internal__sharedSandbox
? (async (...args) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const source = this.__internal__sharedSandbox;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return (await source.__internal__chain).addChain(...args);
})
: this.__internal__wellKnownChains.has(this.__internal__spec)
? client.addWellKnownChain
: client.addChain;
this.__internal__chain = addChain(this.__internal__spec, onResponse).then((chain) => {
hc.setSendJsonRpc(chain.sendJsonRpc);
this.__internal__isChainReady = false;
const cleanup = () => {
// If there are any callbacks left, we have to reject/error them.
// Otherwise, that would cause a memory leak.
const disconnectionError = new Error('Disconnected');
this.__internal__requests.forEach((cb) => cb(disconnectionError));
this.__internal__subscriptions.forEach(([cb]) => cb(disconnectionError));
this.__internal__subscriptions.clear();
};
const staleSubscriptions = [];
const killStaleSubscriptions = () => {
if (staleSubscriptions.length === 0) {
return;
}
const stale = staleSubscriptions.pop();
if (!stale) {
throw new Error('Unable to get stale subscription');
}
const { id, unsubscribeMethod } = stale;
Promise
.race([
this.send(unsubscribeMethod, [id]).catch(noop),
new Promise((resolve) => setTimeout(resolve, 500))
])
.then(killStaleSubscriptions)
.catch(noop);
};
hc.start((health) => {
const isReady = !health.isSyncing && (health.peers > 0 || !health.shouldHavePeers);
// if it's the same as before, then nothing has changed and we are done
if (this.__internal__isChainReady === isReady) {
return;
}
this.__internal__isChainReady = isReady;
if (!isReady) {
// If we've reached this point, that means that the chain used to be "ready"
// and now we are about to emit `disconnected`.
//
// This will cause the PolkadotJs API think that the connection is
// actually dead. In reality the smoldot chain is not dead, of course.
// However, we have to cleanup all the existing callbacks because when
// the smoldot chain stops syncing, then we will emit `connected` and
// the PolkadotJs API will try to re-create the previous
// subscriptions and requests. Although, now is not a good moment
// to be sending unsubscription messages to the smoldot chain, we
// should wait until is no longer syncing to send the unsubscription
// messages from the stale subscriptions of the previous connection.
//
// That's why -before we perform the cleanup of `this.__internal__subscriptions`-
// we keep the necessary information that we will need later on to
// kill the stale subscriptions.
[...this.__internal__subscriptions.values()].forEach((s) => {
staleSubscriptions.push(s[1]);
});
cleanup();
this.__internal__eventemitter.emit('disconnected');
}
else {
killStaleSubscriptions();
this.__internal__eventemitter.emit('connected');
if (this.__internal__resubscribeMethods.size) {
this.__internal__resubscribe();
}
}
});
return objectSpread({}, chain, {
remove: () => {
hc.stop();
chain.remove();
cleanup();
},
sendJsonRpc: hc.sendJsonRpc.bind(hc)
});
});
try {
await this.__internal__chain;
}
catch (e) {
this.__internal__chain = null;
this.__internal__eventemitter.emit('error', e);
throw e;
}
}
__internal__resubscribe = () => {
const promises = [];
this.__internal__resubscribeMethods.forEach((subDetails) => {
// only re-create subscriptions which are not in author (only area where
// transactions are created, i.e. submissions such as 'author_submitAndWatchExtrinsic'
// are not included (and will not be re-broadcast)
if (subDetails.type.startsWith('author_')) {
return;
}
try {
const promise = new Promise((resolve) => {
this.subscribe(subDetails.type, subDetails.method, subDetails.params, subDetails.callback).catch((error) => console.log(error));
resolve();
});
promises.push(promise);
}
catch (error) {
l.error(error);
}
});
Promise.all(promises).catch((err) => l.log(err));
};
async disconnect() {
if (!this.__internal__chain) {
return;
}
const chain = await this.__internal__chain;
this.__internal__chain = null;
this.__internal__isChainReady = false;
try {
chain.remove();
}
catch (_) { }
this.__internal__eventemitter.emit('disconnected');
}
on(type, sub) {
// It's possible. Although, quite unlikely, that by the time that polkadot
// subscribes to the `connected` event, the Provider is already connected.
// In that case, we must emit to let the consumer know that we are connected.
if (type === 'connected' && this.isConnected) {
sub();
}
this.__internal__eventemitter.on(type, sub);
return () => {
this.__internal__eventemitter.removeListener(type, sub);
};
}
async send(method, params) {
if (!this.isConnected || !this.__internal__chain) {
throw new Error('Provider is not connected');
}
const chain = await this.__internal__chain;
const [id, json] = this.__internal__coder.encodeJson(method, params);
const result = new Promise((resolve, reject) => {
this.__internal__requests.set(id, (response) => {
(isError(response) ? reject : resolve)(response);
});
try {
chain.sendJsonRpc(json);
}
catch (e) {
this.__internal__chain = null;
try {
chain.remove();
}
catch (_) { }
this.__internal__eventemitter.emit('error', e);
}
});
try {
return await result;
}
finally {
// let's ensure that once the Promise is resolved/rejected, then we remove
// remove its entry from the internal #requests
this.__internal__requests.delete(id);
}
}
async subscribe(type, method, params, callback) {
if (!subscriptionUnsubscriptionMethods.has(method)) {
throw new Error(`Unsupported subscribe method: ${method}`);
}
const id = await this.send(method, params);
const subscriptionId = `${type}::${id}`;
const cb = (response) => {
if (response instanceof Error) {
callback(response, undefined);
}
else {
callback(null, response);
}
};
const unsubscribeMethod = subscriptionUnsubscriptionMethods.get(method);
if (!unsubscribeMethod) {
throw new Error('Invalid unsubscribe method found');
}
this.__internal__resubscribeMethods.set(subscriptionId, { callback, method, params, type });
this.__internal__subscriptions.set(subscriptionId, [cb, { id, unsubscribeMethod }]);
return id;
}
unsubscribe(type, method, id) {
if (!this.isConnected) {
throw new Error('Provider is not connected');
}
const subscriptionId = `${type}::${id}`;
if (!this.__internal__subscriptions.has(subscriptionId)) {
return Promise.reject(new Error(`Unable to find active subscription=${subscriptionId}`));
}
this.__internal__resubscribeMethods.delete(subscriptionId);
this.__internal__subscriptions.delete(subscriptionId);
return this.send(method, [id]);
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!