UNPKG

ethers

Version:

A complete and compact Ethereum library, for dapps, wallets and any other tools.

200 lines (161 loc) 6.1 kB
import { isError } from "../utils/index.js"; import { PollingEventSubscriber } from "./subscriber-polling.js"; import type { AbstractProvider, Subscriber } from "./abstract-provider.js"; import type { Network } from "./network.js"; import type { EventFilter } from "./provider.js"; import type { JsonRpcApiProvider } from "./provider-jsonrpc.js"; function copy(obj: any): any { return JSON.parse(JSON.stringify(obj)); } /** * Some backends support subscribing to events using a Filter ID. * * When subscribing with this technique, the node issues a unique * //Filter ID//. At this point the node dedicates resources to * the filter, so that periodic calls to follow up on the //Filter ID// * will receive any events since the last call. * * @_docloc: api/providers/abstract-provider */ export class FilterIdSubscriber implements Subscriber { #provider: JsonRpcApiProvider; #filterIdPromise: null | Promise<string>; #poller: (b: number) => Promise<void>; #running: boolean; #network: null | Network; #hault: boolean; /** * Creates a new **FilterIdSubscriber** which will used [[_subscribe]] * and [[_emitResults]] to setup the subscription and provide the event * to the %%provider%%. */ constructor(provider: JsonRpcApiProvider) { this.#provider = provider; this.#filterIdPromise = null; this.#poller = this.#poll.bind(this); this.#running = false; this.#network = null; this.#hault = false; } /** * Sub-classes **must** override this to begin the subscription. */ _subscribe(provider: JsonRpcApiProvider): Promise<string> { throw new Error("subclasses must override this"); } /** * Sub-classes **must** override this handle the events. */ _emitResults(provider: AbstractProvider, result: Array<any>): Promise<void> { throw new Error("subclasses must override this"); } /** * Sub-classes **must** override this handle recovery on errors. */ _recover(provider: AbstractProvider): Subscriber { throw new Error("subclasses must override this"); } async #poll(blockNumber: number): Promise<void> { try { // Subscribe if necessary if (this.#filterIdPromise == null) { this.#filterIdPromise = this._subscribe(this.#provider); } // Get the Filter ID let filterId: null | string = null; try { filterId = await this.#filterIdPromise; } catch (error) { if (!isError(error, "UNSUPPORTED_OPERATION") || error.operation !== "eth_newFilter") { throw error; } } // The backend does not support Filter ID; downgrade to // polling if (filterId == null) { this.#filterIdPromise = null; this.#provider._recoverSubscriber(this, this._recover(this.#provider)); return; } const network = await this.#provider.getNetwork(); if (!this.#network) { this.#network = network; } if ((this.#network as Network).chainId !== network.chainId) { throw new Error("chaid changed"); } if (this.#hault) { return; } const result = await this.#provider.send("eth_getFilterChanges", [ filterId ]); await this._emitResults(this.#provider, result); } catch (error) { console.log("@TODO", error); } this.#provider.once("block", this.#poller); } #teardown(): void { const filterIdPromise = this.#filterIdPromise; if (filterIdPromise) { this.#filterIdPromise = null; filterIdPromise.then((filterId) => { if (this.#provider.destroyed) { return; } this.#provider.send("eth_uninstallFilter", [ filterId ]); }); } } start(): void { if (this.#running) { return; } this.#running = true; this.#poll(-2); } stop(): void { if (!this.#running) { return; } this.#running = false; this.#hault = true; this.#teardown(); this.#provider.off("block", this.#poller); } pause(dropWhilePaused?: boolean): void { if (dropWhilePaused){ this.#teardown(); } this.#provider.off("block", this.#poller); } resume(): void { this.start(); } } /** * A **FilterIdSubscriber** for receiving contract events. * * @_docloc: api/providers/abstract-provider */ export class FilterIdEventSubscriber extends FilterIdSubscriber { #event: EventFilter; /** * Creates a new **FilterIdEventSubscriber** attached to %%provider%% * listening for %%filter%%. */ constructor(provider: JsonRpcApiProvider, filter: EventFilter) { super(provider); this.#event = copy(filter); } _recover(provider: AbstractProvider): Subscriber { return new PollingEventSubscriber(provider, this.#event); } async _subscribe(provider: JsonRpcApiProvider): Promise<string> { const filterId = await provider.send("eth_newFilter", [ this.#event ]); return filterId; } async _emitResults(provider: JsonRpcApiProvider, results: Array<any>): Promise<void> { for (const result of results) { provider.emit(this.#event, provider._wrapLog(result, provider._network)); } } } /** * A **FilterIdSubscriber** for receiving pending transactions events. * * @_docloc: api/providers/abstract-provider */ export class FilterIdPendingSubscriber extends FilterIdSubscriber { async _subscribe(provider: JsonRpcApiProvider): Promise<string> { return await provider.send("eth_newPendingTransactionFilter", [ ]); } async _emitResults(provider: JsonRpcApiProvider, results: Array<any>): Promise<void> { for (const result of results) { provider.emit("pending", result); } } }