diff --git a/.eslintrc b/.eslintrc index a2f71f08..8ba3481a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,18 +1,69 @@ { "env": { - "commonjs": true + "es2020": true, + "node": true, + "webextensions": true, + "worker": true, + "serviceworker": true }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module" }, + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "ignorePatterns": [ + "dist/*" + ], "rules": { + "no-constant-condition": "off", "no-var": "error", "semi": "error", - "indent": "error", "no-multi-spaces": "error", "space-in-parens": "error", "no-multiple-empty-lines": "error", - "prefer-const": "error", - "no-use-before-define": "error" + "prefer-const": [ + "error", + { + "destructuring": "all", + "ignoreReadBeforeAssign": false + } + ], + "camelcase": "error", + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-this-alias": [ + "error", + { + "allowedNames": [ + "self" + ] + } + ] } } diff --git a/package.json b/package.json index cdcc8cbb..ea6912e7 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,21 @@ "scripts": { "build": "tsc", "build:docs": "typedoc", - "lint": "eslint" + "lint": "npx eslint ." }, "author": "", "license": "GPL-3.0", + "engines": { + "node": ">=14" + }, + "type": "module", "devDependencies": { - "@typescript-eslint/eslint-plugin": "^4.24.0", - "eslint": "^7.26.0", - "eslint-config-prettier": "^8.3.0", - "typedoc": "^0.23.9", + "@typescript-eslint/eslint-plugin": "^5.31.0", + "@typescript-eslint/parser": "^5.31.0", + "eslint": "^8.21.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "typedoc": "^0.23.10", "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "^4.2.4" }, diff --git a/src/api.ts b/src/api.ts index 5fda3b11..3af16cc4 100644 --- a/src/api.ts +++ b/src/api.ts @@ -26,7 +26,7 @@ export type ProviderEvents = { /** * Called when inpage provider connects to the extension */ - connected: {}; + connected: Record; /** * Called when inpage provider disconnects from extension @@ -111,7 +111,7 @@ export type ProviderEvents = { /** * Called when the user logs out of the extension */ - loggedOut: {}; + loggedOut: Record; } /** @@ -149,7 +149,7 @@ export type ProviderApi = { * --- * Required permissions: none */ - disconnect: {}; + disconnect: Record; /** * Subscribes to contract updates. @@ -193,7 +193,7 @@ export type ProviderApi = { * --- * Required permissions: none */ - unsubscribeAll: {}; + unsubscribeAll: Record; /** * Returns provider api state @@ -1320,8 +1320,7 @@ export type ProviderMethod = keyof ProviderApi; * @category Provider Api */ export type ProviderApiRequestParams = - ProviderApi[T] extends { input: infer I } ? I - : ProviderApi[T] extends {} ? undefined : never; + ProviderApi[T] extends { input: infer I } ? I : undefined; /** * @category Provider Api @@ -1332,8 +1331,7 @@ export type RawProviderApiRequestParams = ProviderApiR * @category Provider Api */ export type ProviderApiResponse = - ProviderApi[T] extends { output: infer O } ? O - : ProviderApi[T] extends {} ? undefined : never; + ProviderApi[T] extends { output: infer O } ? O : undefined; /** * @category Provider Api diff --git a/src/contract.ts b/src/contract.ts index baa3e5a1..6c46d12f 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -96,7 +96,7 @@ export class Contract { try { // Parent transaction from wallet - let parentTransaction: { transaction: Transaction, possibleMessages: Message[] } | undefined; + let parentTransaction: { transaction: Transaction, possibleMessages: Message[] } | undefined = undefined; // Child transaction promise let resolveChildTransactionPromise: ((transaction: Transaction) => void) | undefined; @@ -188,11 +188,11 @@ export class Contract { async sendExternal(args: SendExternalParams): Promise<{ transaction: Transaction, output?: any }> { await this.provider.ensureInitialized(); - let method = args.withoutSignature === true + const method = args.withoutSignature === true ? this.provider.rawApi.sendUnsignedExternalMessage : this.provider.rawApi.sendExternalMessage; - let { transaction, output } = await method({ + const { transaction, output } = await method({ publicKey: args.publicKey, recipient: this.address.toString(), stateInit: args.stateInit, @@ -212,7 +212,7 @@ export class Contract { async call(args: CallParams = {}): Promise { await this.provider.ensureInitialized(); - let { output, code } = await this.provider.rawApi.runLocal({ + const { output, code } = await this.provider.rawApi.runLocal({ address: this.address.toString(), cachedState: args.cachedState, responsible: args.responsible, @@ -232,7 +232,7 @@ export class Contract { async encodeInternal(): Promise { await this.provider.ensureInitialized(); - let { boc } = await this.provider.rawApi.encodeInternalInput({ + const { boc } = await this.provider.rawApi.encodeInternalInput({ abi: this.abi, method: this.method, params: this.params, @@ -242,8 +242,8 @@ export class Contract { } this._methods = new Proxy({}, { - get: >(_object: {}, method: K) => { - const rawAbi = (this._functions as any)[method]; + get: >(_object: Record, method: K) => { + const rawAbi = this._functions[method]; return (params: AbiFunctionInputs) => new ContractMethodImpl( this._provider, rawAbi, this._abi, this._address, method, params, ); @@ -251,11 +251,11 @@ export class Contract { }) as unknown as ContractMethods; } - public get methods() { + public get methods(): ContractMethods { return this._methods; } - public get address() { + public get address(): Address { return this._address; } @@ -306,7 +306,7 @@ export class Contract { public async getPastEvents(args: GetPastEventParams): Promise> { const { range, filter, limit } = args; - let result: DecodedEventWithTransaction>[] = []; + const result: DecodedEventWithTransaction>[] = []; let currentContinuation = args?.continuation; outer: while (true) { @@ -383,7 +383,7 @@ export class Contract { return undefined; } - let { method, input, output } = result; + const { method, input, output } = result; const rawAbi = this._functions[method]; @@ -435,7 +435,7 @@ export class Contract { return undefined; } - let { method, input } = result; + const { method, input } = result; const rawAbi = this._functions[method]; @@ -460,7 +460,7 @@ export class Contract { return undefined; } - let { method, output } = result; + const { method, output } = result; const rawAbi = this._functions[method]; @@ -485,7 +485,7 @@ export class Contract { return undefined; } - let { event, data } = result; + const { event, data } = result; const rawAbi = this._events[event]; diff --git a/src/index.ts b/src/index.ts index 023e1538..753dac4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,11 +24,7 @@ import { parseTransaction, serializeTokensObject, } from './models'; -import { - Address, - AddressLiteral, - getUniqueId, -} from './utils'; +import { Address, getUniqueId } from './utils'; import * as subscriber from './stream'; import * as contract from './contract'; @@ -73,10 +69,19 @@ export type ProviderProperties = { fallback?: () => Promise; }; +declare global { + interface Window { + __ever: Provider | undefined; + __hasEverscaleProvider: boolean | undefined; + ton: Provider | undefined; + hasTonProvider: boolean | undefined; + } +} + const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; let ensurePageLoaded: Promise; -if (!isBrowser || document.readyState == 'complete') { +if (!isBrowser || document.readyState === 'complete') { ensurePageLoaded = Promise.resolve(); } else { ensurePageLoaded = new Promise((resolve) => { @@ -86,7 +91,7 @@ if (!isBrowser || document.readyState == 'complete') { }); } -const getProvider = (): Provider | undefined => isBrowser ? ((window as any).__ever || (window as any).ton) : undefined; +const getProvider = (): Provider | undefined => isBrowser ? window.__ever || window.ton : undefined; /** * @category Provider @@ -97,8 +102,7 @@ export async function hasEverscaleProvider(): Promise { } await ensurePageLoaded; - return (window as Record).__hasEverscaleProvider === true || - (window as Record).hasTonProvider === true; + return window.__hasEverscaleProvider === true || window.hasTonProvider === true; } /** @@ -108,8 +112,17 @@ export class ProviderRpcClient { private readonly _properties: ProviderProperties; private readonly _api: RawProviderApiMethods; private readonly _initializationPromise: Promise; - private readonly _subscriptions: { [K in ProviderEvent]?: { [id: number]: (data: ProviderEventData) => void } } = {}; - private readonly _contractSubscriptions: { [address: string]: { [id: number]: ContractUpdatesSubscription } } = {}; + private readonly _subscriptions: { [K in ProviderEvent]: Map) => void> } = { + connected: new Map(), + disconnected: new Map(), + transactionsFound: new Map(), + contractStateChanged: new Map(), + messageStatusUpdated: new Map(), + networkChanged: new Map(), + permissionsChanged: new Map(), + loggedOut: new Map(), + }; + private readonly _contractSubscriptions: Map> = new Map(); private _provider?: Provider; public Contract: new (abi: Abi, address: Address) => contract.Contract; @@ -143,9 +156,9 @@ export class ProviderRpcClient { get: ( _object: ProviderRpcClient, method: K, - ) => (params?: RawProviderApiRequestParams) => { + ) => (params: RawProviderApiRequestParams) => { if (this._provider != null) { - return this._provider.request({ method, params: params! }); + return this._provider.request({ method, params }); } else { throw new ProviderNotInitializedException(); } @@ -179,8 +192,8 @@ export class ProviderRpcClient { if (this._provider != null) { resolve(); } else { - const eventName = (window as Record).__hasEverscaleProvider === true ? 'ever#initialized' : 'ton#initialized'; - window.addEventListener(eventName, (_data) => { + const eventName = window.__hasEverscaleProvider === true ? 'ever#initialized' : 'ton#initialized'; + window.addEventListener(eventName, (_) => { this._provider = getProvider(); resolve(); }); @@ -344,11 +357,14 @@ export class ProviderRpcClient { public subscribe(eventName: 'loggedOut'): Promise>; public async subscribe(eventName: T, params?: { address: Address }): Promise> { + type Handler = + K extends 'data' ? (data: ProviderEventData) => void : () => void; + class SubscriptionImpl implements Subscription { - private readonly _listeners: { [K in SubscriptionEvent]: ((data?: any) => void)[] } = { - ['data']: [], - ['subscribed']: [], - ['unsubscribed']: [], + private readonly _listeners: { [K in SubscriptionEvent]: Handler[] } = { + data: [], + subscribed: [], + unsubscribed: [], }; private _subscribed = false; @@ -360,7 +376,7 @@ export class ProviderRpcClient { on(eventName: 'data', listener: (data: ProviderEventData) => void): this; on(eventName: 'subscribed', listener: () => void): this; on(eventName: 'unsubscribed', listener: () => void): this; - on(eventName: SubscriptionEvent, listener: ((data: ProviderEventData) => void) | (() => void)): this { + on(eventName: K, listener: Handler): this { this._listeners[eventName].push(listener); return this; } @@ -396,7 +412,7 @@ export class ProviderRpcClient { } } - let existingSubscriptions = this._getEventSubscriptions(eventName); + const existingSubscriptions = this._subscriptions[eventName]; const id = getUniqueId(); @@ -407,74 +423,79 @@ export class ProviderRpcClient { case 'permissionsChanged': case 'loggedOut': { const subscription = new SubscriptionImpl(async (subscription) => { - if (existingSubscriptions[id] != null) { + if (existingSubscriptions.has(id)) { return; } - existingSubscriptions[id] = (data) => { + existingSubscriptions.set(id, (data) => { subscription.notify(data); - }; + }); }, async () => { - delete existingSubscriptions[id]; + existingSubscriptions.delete(id); }); await subscription.subscribe(); return subscription; } case 'transactionsFound': case 'contractStateChanged': { + if (params == null) { + throw new Error('Address must be specified for the subscription'); + } + await this.ensureInitialized(); - const address = params!.address.toString(); + const address = params.address.toString(); const subscription = new SubscriptionImpl(async (subscription) => { - if (existingSubscriptions[id] != null) { + if (existingSubscriptions.has(id)) { return; } - existingSubscriptions[id] = ((data: ProviderEventData<'transactionsFound' | 'contractStateChanged'>) => { - if (data.address.toString() == address) { + existingSubscriptions.set(id, ((data: ProviderEventData<'transactionsFound' | 'contractStateChanged'>) => { + if (data.address.toString() === address) { subscription.notify(data as ProviderEventData); } - }) as (data: ProviderEventData) => void; + }) as (data: ProviderEventData) => void); - let contractSubscriptions = this._contractSubscriptions[address]; + let contractSubscriptions = this._contractSubscriptions.get(address); if (contractSubscriptions == null) { - contractSubscriptions = {}; - this._contractSubscriptions[address] = contractSubscriptions; + contractSubscriptions = new Map(); + this._contractSubscriptions.set(address, contractSubscriptions); } - contractSubscriptions[id] = { - state: eventName == 'contractStateChanged', - transactions: eventName == 'transactionsFound', + const subscriptionState = { + state: eventName === 'contractStateChanged', + transactions: eventName === 'transactionsFound', }; + contractSubscriptions.set(id, subscriptionState); const { total, withoutExcluded, - } = foldSubscriptions(Object.values(contractSubscriptions), contractSubscriptions[id]); + } = foldSubscriptions(contractSubscriptions.values(), subscriptionState); try { - if (total.transactions != withoutExcluded.transactions || total.state != withoutExcluded.state) { + if (total.transactions !== withoutExcluded.transactions || total.state !== withoutExcluded.state) { await this.rawApi.subscribe({ address, subscriptions: total }); } } catch (e) { - delete existingSubscriptions[id]; - delete contractSubscriptions[id]; + existingSubscriptions.delete(id); + contractSubscriptions.delete(id); throw e; } }, async () => { - delete existingSubscriptions[id]; + existingSubscriptions.delete(id); - const contractSubscriptions = this._contractSubscriptions[address]; + const contractSubscriptions = this._contractSubscriptions.get(address); if (contractSubscriptions == null) { return; } - const updates = contractSubscriptions[id]; + const updates = contractSubscriptions.get(id); - const { total, withoutExcluded } = foldSubscriptions(Object.values(contractSubscriptions), updates); - delete contractSubscriptions[id]; + const { total, withoutExcluded } = foldSubscriptions(contractSubscriptions.values(), updates); + contractSubscriptions.delete(id); if (!withoutExcluded.transactions && !withoutExcluded.state) { await this.rawApi.unsubscribe({ address }); - } else if (total.transactions != withoutExcluded.transactions || total.state != withoutExcluded.state) { + } else if (total.transactions !== withoutExcluded.transactions || total.state !== withoutExcluded.state) { await this.rawApi.subscribe({ address, subscriptions: withoutExcluded }); } }); @@ -873,28 +894,13 @@ export class ProviderRpcClient { for (const [eventName, extractor] of Object.entries(knownEvents)) { provider.addListener(eventName as ProviderEvent, (data) => { const handlers = this._subscriptions[eventName as ProviderEvent]; - if (handlers == null) { - return; - } const parsed = (extractor as any)(data); - for (const handler of Object.values(handlers)) { + for (const handler of handlers.values()) { handler(parsed); } }); } } - - private _getEventSubscriptions( - eventName: T, - ): ({ [id: number]: (data: ProviderEventData) => void }) { - let existingSubscriptions = this._subscriptions[eventName]; - if (existingSubscriptions == null) { - existingSubscriptions = {}; - this._subscriptions[eventName] = existingSubscriptions; - } - - return existingSubscriptions as { [id: number]: (data: ProviderEventData) => void }; - } } /** @@ -959,9 +965,9 @@ export class ProviderNotInitializedException extends Error { /** * @category Provider */ -export type RawRpcMethod

= RawProviderApiRequestParams

extends {} - ? (args: RawProviderApiRequestParams

) => Promise> - : () => Promise> +export type RawRpcMethod

= RawProviderApiRequestParams

extends undefined + ? () => Promise> + : (args: RawProviderApiRequestParams

) => Promise> /** * @category Provider @@ -1038,7 +1044,7 @@ export type AddAssetParams = { function foldSubscriptions( subscriptions: Iterable, - except: ContractUpdatesSubscription, + except?: ContractUpdatesSubscription, ): { total: ContractUpdatesSubscription, withoutExcluded: ContractUpdatesSubscription } { const total = { state: false, transactions: false }; const withoutExcluded = Object.assign({}, total); @@ -1050,7 +1056,7 @@ function foldSubscriptions( total.state ||= item.state; total.transactions ||= item.transactions; - if (item != except) { + if (item !== except) { withoutExcluded.state ||= item.state; withoutExcluded.transactions ||= item.transactions; } diff --git a/src/models.ts b/src/models.ts index 45e776ba..97a1143f 100644 --- a/src/models.ts +++ b/src/models.ts @@ -294,15 +294,6 @@ export type EncryptedData = { /* ABI stuff */ -/** - * @category Models - */ -export type SignedMessage = { - bodyHash: string; - expireAt: number; - boc: string; -}; - /** * @category Models */ @@ -349,11 +340,6 @@ export type FunctionCall = { params: TokensObject; } -/** - * @category Models - */ -export type RawFunctionCall = FunctionCall; - type AbiParamKindUint = 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint64' | 'uint128' | 'uint160' | 'uint256'; type AbiParamKindInt = 'int8' | 'int16' | 'int24' | 'int32' | 'int64' | 'int128' | 'int160' | 'int256'; type AbiParamKindTuple = 'tuple'; @@ -480,7 +466,7 @@ function parseTokenValue(param: AbiParam, token: RawTokenValue): TokenValue { const rawParam = { name: param.name, type: rawType, components: param.components } as AbiParam; return parseTokenValue(rawParam, token); } - } else if (rawType == 'tuple') { + } else if (rawType === 'tuple') { type TokenValueTuple = { [K in string]: TokenValue }; const result: TokenValueTuple

= {}; @@ -490,7 +476,7 @@ function parseTokenValue(param: AbiParam, token: RawTokenValue): TokenValue { } } return result; - } else if (rawType == 'address') { + } else if (rawType === 'address') { return new Address(token as string) as TokenValue; } else { return token; @@ -517,15 +503,6 @@ function parseTokenValue(param: AbiParam, token: RawTokenValue): TokenValue { } } -/** - * @category Models - */ -export type HeadersObject = { - pubkey?: string; - expire?: string | number; - time?: string | number; -}; - type InputTokenValue = T extends AbiParamKindUint | AbiParamKindInt | AbiParamKindGram | AbiParamKindTime | AbiParamKindExpire ? string | number : T extends AbiParamKindBool ? boolean @@ -566,7 +543,7 @@ export type OutputTokenObject = O extends { name: infer K, type: infer T, com export type MergeInputObjectsArray = A extends readonly [infer T, ...infer Ts] ? (InputTokenObject & MergeInputObjectsArray<[...Ts]>) - : A extends readonly [infer T] ? InputTokenObject : A extends readonly [] ? {} : never; + : A extends readonly [infer T] ? InputTokenObject : A extends readonly [] ? Record : never; /** * @category Models @@ -574,7 +551,7 @@ export type MergeInputObjectsArray = export type MergeOutputObjectsArray = A extends readonly [infer T, ...infer Ts] ? (OutputTokenObject & MergeOutputObjectsArray<[...Ts]>) - : A extends readonly [infer T] ? OutputTokenObject : A extends readonly [] ? {} : never; + : A extends readonly [infer T] ? OutputTokenObject : A extends readonly [] ? Record : never; type AbiFunction = C extends { functions: infer F } ? F extends readonly unknown[] ? ArrayItemType : never : never; type AbiEvent = C extends { events: infer E } ? E extends readonly unknown[] ? ArrayItemType : never : never; diff --git a/src/stream.ts b/src/stream.ts index 24020fa7..28901823 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -5,17 +5,17 @@ import { ProviderRpcClient, Subscription } from './index'; type SubscriptionWithAddress = Extract +type EventHandler = { + onData: (event: ProviderEventData) => Promise, + onEnd: (eof: boolean) => void, + queue: PromiseQueue + state: { eof: boolean, finished: boolean }, +}; + type SubscriptionsWithAddress = { [K in SubscriptionWithAddress]?: { subscription: Promise> - handlers: { - [id: string]: { - onData: (event: ProviderEventData) => Promise, - onEnd: (eof: boolean) => void, - queue: PromiseQueue - state: { eof: boolean, finished: boolean }, - } - } + handlers: Map> } }; @@ -23,8 +23,8 @@ type SubscriptionsWithAddress = { * @category Stream */ export class Subscriber { - private readonly subscriptions: { [address: string]: SubscriptionsWithAddress } = {}; - private readonly scanners: { [id: number]: Scanner } = {}; + private readonly subscriptions: Map = new Map(); + private readonly scanners: Map = new Map(); constructor(private readonly provider: ProviderRpcClient) { } @@ -49,20 +49,20 @@ export class Subscriber { origin: transaction, onData, onEnd: (eof) => { - delete this.scanners[id]; + this.scanners.delete(id); onEnd(eof); }, }); - this.scanners[id] = scanner; + this.scanners.set(id, scanner); scanner.start(); // Subscription is not required return Promise.resolve(); }, async () => { - const scanner = this.scanners[id]; - delete this.scanners[id]; + const scanner = this.scanners.get(id); if (scanner != null) { + this.scanners.delete(id); await scanner.stop(); } }, @@ -86,21 +86,21 @@ export class Subscriber { address, onData, onEnd: (eof) => { - delete this.scanners[id]; + this.scanners.delete(id); onEnd(eof); }, ...filter, }); - this.scanners[id] = scanner; + this.scanners.set(id, scanner); scanner.start(); // Subscription is not required return Promise.resolve(); }, async () => { - const scanner = this.scanners[id]; - delete this.scanners[id]; + const scanner = this.scanners.get(id); if (scanner != null) { + this.scanners.delete(id); await scanner.stop(); } }, @@ -116,39 +116,29 @@ export class Subscriber { public unsubscribe = async (): Promise => this._unsubscribe(); private async _unsubscribe(): Promise { - const subscriptions = Object.assign({}, this.subscriptions); - for (const address of Object.keys(this.subscriptions)) { - delete this.subscriptions[address]; - } + const tasks: Promise[] = []; - const scanners = Object.assign({}, this.scanners); - for (const id of Object.keys(this.scanners)) { - delete this.scanners[id as any]; + for (const item of this.subscriptions.values()) { + for (const [event, eventData] of Object.entries(item)) { + delete item[event as unknown as SubscriptionWithAddress]; + if (eventData != null) { + tasks.push( + eventData.subscription + .then(item => item.unsubscribe()) + .catch(() => { /* ignore */ + }), + ); + } + } } + this.subscriptions.clear(); - await Promise.all( - Object.values(subscriptions) - .map(async (item: SubscriptionsWithAddress) => { - const events = Object.assign({}, item); - for (const event of Object.keys(events)) { - delete item[event as unknown as SubscriptionWithAddress]; - } - - await Promise.all( - Object.values(events).map((eventData) => { - if (eventData == null) { - return; - } + for (const scanner of this.scanners.values()) { + tasks.push(scanner.stop()); + } + this.scanners.clear(); - return eventData.subscription.then((item: Subscription) => { - return item.unsubscribe(); - }).catch(() => { - // ignore - }); - }), - ); - }).concat(Object.values(scanners).map((item) => item.stop())), - ); + await Promise.all(tasks); } private _addSubscription( @@ -160,8 +150,8 @@ export class Subscriber { const rawAddress = address.toString(); - const stopProducer = (id: string) => { - const subscriptions = this.subscriptions[rawAddress] as SubscriptionsWithAddress | undefined; + const stopProducer = (id: number) => { + const subscriptions = this.subscriptions.get(rawAddress); if (subscriptions == null) { // No subscriptions for the address return; @@ -169,10 +159,10 @@ export class Subscriber { const eventData = subscriptions[event] as EventData | undefined; if (eventData != null) { - const handler = eventData.handlers[id] as EventData['handlers'][number] | undefined; + const handler = eventData.handlers.get(id); if (handler != null) { // Remove event handler with the id - delete eventData.handlers[id]; + eventData.handlers.delete(id); const { queue, onEnd, state } = handler; if (!state.finished) { @@ -183,7 +173,7 @@ export class Subscriber { } // Remove event data subscription if there are none of them - if (Object.keys(eventData.handlers).length === 0) { + if (eventData.handlers.size === 0) { const subscription = eventData.subscription as Promise>; delete subscriptions[event]; @@ -194,47 +184,48 @@ export class Subscriber { } // Remove address subscriptions object if it is empty - if (Object.keys(subscriptions).length === 0) { - delete this.subscriptions[rawAddress]; + if (subscriptions.contractStateChanged == null && subscriptions.transactionsFound == null) { + this.subscriptions.delete(rawAddress); } }; - const id = getUniqueId().toString(); + const id = getUniqueId(); return new StreamImpl( (onData, onEnd) => { - let subscriptions = this.subscriptions[rawAddress] as SubscriptionsWithAddress | undefined; + const subscriptions = this.subscriptions.get(rawAddress); let eventData = subscriptions?.[event] as EventData | undefined; const state = { eof: false, finished: false }; // Create handler object - const handler = { + const handler: EventHandler = { onData, onEnd, queue: new PromiseQueue(), state, - } as EventData['handlers'][number]; + }; if (eventData != null) { // Add handler if there is already a handler group - eventData.handlers[id] = handler; + eventData.handlers.set(id, handler); return Promise.resolve(); } // Create handlers group - const handlers = { - [id]: handler, - } as EventData['handlers']; + const handlers: EventData['handlers'] = new Map(); + handlers.set(id, handler); + + type ProviderMethod = (eventName: T, params: { address: Address }) => Promise>; // Create subscription - const subscription = (this.provider.subscribe as any)(event, { address }) + const subscription = (this.provider.subscribe as unknown as ProviderMethod)(event, { address }) .then((subscription: Subscription) => { subscription.on('data', (data) => { - Object.values(handlers).forEach(({ onData, queue, state }) => { + for (const { onData, queue, state } of handlers.values()) { // Skip closed streams if (state.eof || state.finished) { - return; + continue; } queue.enqueue(async () => { @@ -243,28 +234,32 @@ export class Subscriber { stopProducer(id); } }); - }); + } }); subscription.on('unsubscribed', () => { - Object.keys(handlers).forEach(stopProducer); + for (const id of handlers.keys()) { + stopProducer(id); + } }); return subscription; }).catch((e: Error) => { console.error(e); - Object.keys(handlers).forEach(stopProducer); + for (const id of handlers.keys()) { + stopProducer(id); + } throw e; }); // Add event data to subscriptions eventData = { subscription, handlers } as EventData; if (subscriptions == null) { - this.subscriptions[rawAddress] = { [event]: eventData }; + this.subscriptions.set(rawAddress, { [event]: eventData }); } else { subscriptions[event] = eventData; } // Wait until subscribed - return subscription.then(() => { + return subscription.then(() => { /* do nothing */ }); }, () => stopProducer(id), @@ -396,7 +391,7 @@ export type Delayed = { } & (F extends true ? { fold: MakeDelayedPromise['fold']>, finished: MakeDelayedPromise['finished']>, -} : {}); +} : Record); /** * @category Stream @@ -449,7 +444,7 @@ class StreamImpl implements Stream { public first(ctx?: { subscribed?: Promise }): Promise { type R = F extends true ? T | undefined : T; - let state: { found: false } | { found: true, result: T } = { found: false }; + const state: { found: boolean, result?: T } = { found: false }; return new Promise((resolve: (value: R) => void, reject) => { const subscribed = this.makeProducer( @@ -539,7 +534,7 @@ class StreamImpl implements Stream { await handler(item); return true; }), - (_eof) => { + (_eof) => { /* do nothing */ }, ); @@ -569,7 +564,7 @@ class StreamImpl implements Stream { return Promise.all([ this.makeProducer(onData, checkEnd), other.makeProducer(onData, checkEnd), - ]).then(() => { + ]).then(() => { /* do nothing */ }); }, () => { @@ -795,7 +790,7 @@ class UnorderedTransactionsScanner implements Scanner { private continuation?: TransactionId; private promise?: Promise; - private isRunning: boolean = false; + private isRunning = false; constructor( private readonly provider: ProviderRpcClient, @@ -811,7 +806,7 @@ class UnorderedTransactionsScanner implements Scanner { this.isRunning = true; this.promise = (async () => { const params = this.params; - let state = { + const state = { complete: false, }; @@ -899,7 +894,7 @@ class TraceTransactionsScanner implements Scanner { private readonly queue: PromiseQueue = new PromiseQueue(); private promise?: Promise; - private isRunning: boolean = false; + private isRunning = false; private readonly streams: Map> = new Map(); private readonly pendingTransactions: Map = new Map(); @@ -951,9 +946,9 @@ class TraceTransactionsScanner implements Scanner { if (pendingTransaction == null) { this.pendingTransactions.set(messageHash, { promise: Promise.resolve(transaction), - resolve: () => { + resolve: () => { /* do nothing */ }, - reject: () => { + reject: () => { /* do nothing */ }, }); } else { @@ -1016,7 +1011,8 @@ class TraceTransactionsScanner implements Scanner { transactionsQueue.push(childTransaction); } } - } catch (e: any) { + } catch (e: unknown) { + /* do nothing */ } finally { this.queue.enqueue(async () => this.params.onEnd(state.complete)); this.isRunning = false; @@ -1050,11 +1046,11 @@ class TraceTransactionsScanner implements Scanner { class PromiseQueue { private readonly queue: (() => Promise)[] = []; - private workingOnPromise: boolean = false; + private workingOnPromise = false; public enqueue(promise: () => Promise) { this.queue.push(promise); - this._dequeue().catch(() => { + this._dequeue().catch(() => { /* do nothing */ }); } diff --git a/src/utils.ts b/src/utils.ts index 9d9b3ab3..d0a33e1c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -43,9 +43,9 @@ export class Address { private _equals(other: Address | string): boolean { if (other instanceof Address) { - return this._address == other._address; + return this._address === other._address; } else { - return this._address == other; + return this._address === other; } } } @@ -99,7 +99,7 @@ export function mergeTransactions( newTransactions: Transaction[], info: TransactionsBatchInfo, ): Transaction[] { - if (info.batchType == 'old') { + if (info.batchType === 'old') { knownTransactions.push(...newTransactions); return knownTransactions; } diff --git a/tsconfig.json b/tsconfig.json index 200d013d..c86e07fd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "es2018", - "module": "commonjs", + "target": "ES2020", + "module": "ES2020", "outDir": "./dist", "strict": true, "noUnusedParameters": true,