From d8c204f4ea01ddefb68dda97d2719c78a72ec1cc Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Tue, 6 Aug 2024 17:59:25 +0200 Subject: [PATCH] feat: providers loading ready --- .../providers/ChainAgnosticProvider.ts | 36 ++++++++++++++++--- src/background/providers/CoreProvider.ts | 30 +++------------- .../providers/initializeInpageProvider.ts | 2 +- .../providers/utils/ProviderReadyPromise.ts | 25 +++++++------ 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index 6475a55f7..3df1b647b 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -1,5 +1,6 @@ import EventEmitter from 'events'; import { + DAppProviderRequest, JsonRpcRequest, JsonRpcRequestPayload, } from '../connections/dAppConnection/models'; @@ -8,9 +9,18 @@ import { ethErrors, serializeError } from 'eth-rpc-errors'; import AbstractConnection from '../utils/messaging/AbstractConnection'; import { ChainId } from '@avalabs/core-chains-sdk'; import RequestRatelimiter from './utils/RequestRatelimiter'; +import { + InitializationStep, + ProviderReadyPromise, +} from './utils/ProviderReadyPromise'; +import onDomReady from './utils/onDomReady'; +import { getSiteMetadata } from './utils/getSiteMetadata'; export class ChainAgnosticProvider extends EventEmitter { #contentScriptConnection: AbstractConnection; + #providerReadyPromise = new ProviderReadyPromise([ + InitializationStep.DOMAIN_METADATA_SENT, + ]); #requestRateLimiter = new RequestRatelimiter([ 'eth_requestAccounts', @@ -19,12 +29,26 @@ export class ChainAgnosticProvider extends EventEmitter { constructor(connection) { super(); + connection.connect(); this.#contentScriptConnection = connection; this.#init(); } async #init() { await this.#contentScriptConnection.connect(); + + onDomReady(async () => { + const domainMetadata = await getSiteMetadata(); + + this.#request({ + data: { + method: DAppProviderRequest.DOMAIN_METADATA_METHOD, + params: domainMetadata, + }, + }); + + this.#providerReadyPromise.check(InitializationStep.DOMAIN_METADATA_SENT); + }); } #request = async ({ @@ -73,12 +97,14 @@ export class ChainAgnosticProvider extends EventEmitter { chainId, }: { data: PartialBy; - sessionId: string; - chainId: string | null; + sessionId?: string; + chainId?: string | null; }) => { - return this.#requestRateLimiter.call(data.method, () => - this.#request({ data, chainId, sessionId }) - ); + return this.#providerReadyPromise.call(() => { + return this.#requestRateLimiter.call(data.method, () => + this.#request({ data, chainId, sessionId }) + ); + }); }; subscribeToMessage = (callback) => { diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 59ed12d33..8fbc56739 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -1,7 +1,5 @@ import { ethErrors } from 'eth-rpc-errors'; import EventEmitter from 'events'; -import { getSiteMetadata } from './utils/getSiteMetadata'; -import onDomReady from './utils/onDomReady'; import { EventNames, type AccountsChangedEventData, @@ -18,7 +16,6 @@ import { } from '../connections/dAppConnection/models'; import type { PartialBy, ProviderInfo } from '../models'; import { ChainAgnosticProvider } from './ChainAgnosticProvider'; -import AbstractConnection from '../utils/messaging/AbstractConnection'; interface ProviderState { accounts: string[] | null; @@ -28,8 +25,9 @@ interface ProviderState { } export class CoreProvider extends EventEmitter { - #providerReadyPromise = new ProviderReadyPromise(); - #contentScriptConnection: AbstractConnection; + #providerReadyPromise = new ProviderReadyPromise([ + InitializationStep.PROVIDER_STATE_LOADED, + ]); #chainagnosticProvider?: ChainAgnosticProvider; readonly info: ProviderInfo = { @@ -68,16 +66,9 @@ export class CoreProvider extends EventEmitter { isUnlocked: () => Promise.resolve(this._isUnlocked), }; - constructor({ - connection, - maxListeners = 100, - }: { - connection: AbstractConnection; - maxListeners?: number; - }) { + constructor({ maxListeners = 100 }: { maxListeners?: number }) { super(); this.setMaxListeners(maxListeners); - this.#contentScriptConnection = connection; this.#subscribe(); } @@ -104,19 +95,6 @@ export class CoreProvider extends EventEmitter { * Initializes provider state, and collects dApp information */ #init = async () => { - await this.#contentScriptConnection.connect(); - - onDomReady(async () => { - const domainMetadata = await getSiteMetadata(); - - this.#request({ - method: DAppProviderRequest.DOMAIN_METADATA_METHOD, - params: domainMetadata, - }); - - this.#providerReadyPromise.check(InitializationStep.DOMAIN_METADATA_SENT); - }); - try { const response = await this.#request({ method: DAppProviderRequest.INIT_DAPP_STATE, diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index be30cd57b..8e88905c4 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -24,7 +24,7 @@ export function initializeProvider( } ); - const provider = new Proxy(new CoreProvider({ maxListeners, connection }), { + const provider = new Proxy(new CoreProvider({ maxListeners }), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, }); diff --git a/src/background/providers/utils/ProviderReadyPromise.ts b/src/background/providers/utils/ProviderReadyPromise.ts index 4db3e72da..fd8fb09b6 100644 --- a/src/background/providers/utils/ProviderReadyPromise.ts +++ b/src/background/providers/utils/ProviderReadyPromise.ts @@ -1,32 +1,37 @@ export enum InitializationStep { - DOMAIN_METADATA_SENT, - PROVIDER_STATE_LOADED, + DOMAIN_METADATA_SENT = 'domain_metadata_sent', + PROVIDER_STATE_LOADED = 'provider_state_loaded', } export class ProviderReadyPromise { - #steps: boolean[] = []; + #unpreparedSteps: InitializationStep[] = []; #inflightRequests: { resolve(value: unknown): void; fn(): Promise; }[] = []; - constructor() { - // length / 2 is required since InitializationStep is an enum - // enums generate objects like this: { key0: 0, key1: 1, 0: key0, 1: key1 } - this.#steps = Array(Object.keys(InitializationStep).length / 2).fill(false); + constructor(steps: InitializationStep[]) { + this.#unpreparedSteps = Object.values(steps); } check = (step: InitializationStep) => { - this.#steps[step] = true; + const stepIndex = this.#unpreparedSteps.findIndex( + (currentStep) => step === currentStep + ); + + if (stepIndex > -1) { + this.#unpreparedSteps.splice(stepIndex, 1); + } + this._proceed(); }; uncheck = (step: InitializationStep) => { - this.#steps[step] = false; + this.#unpreparedSteps[step] = step; }; private _proceed = () => { - if (this.#steps.some((step) => !step)) { + if (this.#unpreparedSteps.length) { return; }