-
-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed PollingController to be a mixin so it can be used with both V1 …
…and V2 controllers
- Loading branch information
1 parent
ff9e2b3
commit ddf7c70
Showing
2 changed files
with
112 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,150 +1,136 @@ | ||
import { BaseControllerV2 } from '@metamask/base-controller'; | ||
import type { | ||
RestrictedControllerMessenger, | ||
StateMetadata, | ||
} from '@metamask/base-controller'; | ||
import { BaseController, BaseControllerV2 } from '@metamask/base-controller'; | ||
import type { NetworkClientId } from '@metamask/network-controller'; | ||
import type { Json } from '@metamask/utils'; | ||
import { v4 as random } from 'uuid'; | ||
|
||
export type PollingCompleteType<N extends string> = { | ||
type: `${N}:pollingComplete`; | ||
payload: [string]; | ||
}; | ||
|
||
type Constructor = new (...args: any[]) => {}; | ||
|
||
/** | ||
* PollingController is an abstract class that implements the polling | ||
* functionality for a controller. It is meant to be extended by a controller | ||
* that needs to poll for data by networkClientId. | ||
* PollingControllerMixin | ||
* | ||
* @param Base - The base class to mix onto. | ||
* @returns The mixin. | ||
*/ | ||
export default abstract class PollingController< | ||
Name extends string, | ||
State extends Record<string, Json>, | ||
messenger extends RestrictedControllerMessenger< | ||
Name, | ||
any, | ||
PollingCompleteType<Name> | any, | ||
string, | ||
string | ||
>, | ||
> extends BaseControllerV2<Name, State, messenger> { | ||
readonly #intervalLength: number; | ||
function PollingControllerMixin<TBase extends Constructor>(Base: TBase) { | ||
/** | ||
* PollingController is an abstract class that implements the polling | ||
* functionality for a controller. It is meant to be extended by a controller | ||
* that needs to poll for data by networkClientId. | ||
* | ||
*/ | ||
abstract class PollingControllerBase extends Base { | ||
readonly #networkClientIdTokensMap: Map<NetworkClientId, Set<string>> = | ||
new Map(); | ||
|
||
private readonly networkClientIdTokensMap: Map<NetworkClientId, Set<string>> = | ||
new Map(); | ||
readonly #intervalIds: Record<NetworkClientId, NodeJS.Timeout> = {}; | ||
|
||
private readonly intervalIds: Record<NetworkClientId, NodeJS.Timeout> = {}; | ||
#callbacks: Set<(networkClientId: NetworkClientId) => void> = new Set(); | ||
|
||
constructor({ | ||
name, | ||
state, | ||
messenger, | ||
metadata, | ||
pollingIntervalLength, | ||
}: { | ||
name: Name; | ||
state: State; | ||
metadata: StateMetadata<State>; | ||
messenger: messenger; | ||
pollingIntervalLength: number; | ||
}) { | ||
super({ | ||
name, | ||
state, | ||
messenger, | ||
metadata, | ||
}); | ||
#intervalLength = 1000; | ||
|
||
if (!pollingIntervalLength) { | ||
throw new Error('pollingIntervalLength required for PollingController'); | ||
getIntervalLength() { | ||
return this.#intervalLength; | ||
} | ||
|
||
this.#intervalLength = pollingIntervalLength; | ||
} | ||
setIntervalLength(length: number) { | ||
this.#intervalLength = length; | ||
} | ||
|
||
/** | ||
* Starts polling for a networkClientId | ||
* | ||
* @param networkClientId - The networkClientId to start polling for | ||
* @returns void | ||
*/ | ||
start(networkClientId: NetworkClientId) { | ||
const innerPollToken = random(); | ||
if (this.networkClientIdTokensMap.has(networkClientId)) { | ||
const set = this.networkClientIdTokensMap.get(networkClientId); | ||
set?.add(innerPollToken); | ||
} else { | ||
const set = new Set<string>(); | ||
set.add(innerPollToken); | ||
this.networkClientIdTokensMap.set(networkClientId, set); | ||
/** | ||
* Starts polling for a networkClientId | ||
* | ||
* @param networkClientId - The networkClientId to start polling for | ||
* @returns void | ||
*/ | ||
start(networkClientId: NetworkClientId) { | ||
const innerPollToken = random(); | ||
if (this.#networkClientIdTokensMap.has(networkClientId)) { | ||
const set = this.#networkClientIdTokensMap.get(networkClientId); | ||
set?.add(innerPollToken); | ||
} else { | ||
const set = new Set<string>(); | ||
set.add(innerPollToken); | ||
this.#networkClientIdTokensMap.set(networkClientId, set); | ||
} | ||
this.#poll(networkClientId); | ||
return innerPollToken; | ||
} | ||
this.#poll(networkClientId); | ||
return innerPollToken; | ||
} | ||
|
||
/** | ||
* Stops polling for all networkClientIds | ||
*/ | ||
stopAll() { | ||
this.networkClientIdTokensMap.forEach((tokens, _networkClientId) => { | ||
tokens.forEach((token) => { | ||
this.stop(token); | ||
/** | ||
* Stops polling for all networkClientIds | ||
*/ | ||
stopAll() { | ||
this.#networkClientIdTokensMap.forEach((tokens, _networkClientId) => { | ||
tokens.forEach((token) => { | ||
this.stop(token); | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Stops polling for a networkClientId | ||
* | ||
* @param pollingToken - The polling token to stop polling for | ||
*/ | ||
stop(pollingToken: string) { | ||
if (!pollingToken) { | ||
throw new Error('pollingToken required'); | ||
} | ||
let found = false; | ||
this.networkClientIdTokensMap.forEach((tokens, networkClientId) => { | ||
if (tokens.has(pollingToken)) { | ||
found = true; | ||
this.networkClientIdTokensMap | ||
.get(networkClientId) | ||
?.delete(pollingToken); | ||
if (this.networkClientIdTokensMap.get(networkClientId)?.size === 0) { | ||
clearTimeout(this.intervalIds[networkClientId]); | ||
delete this.intervalIds[networkClientId]; | ||
this.networkClientIdTokensMap.delete(networkClientId); | ||
this.messagingSystem.publish( | ||
`${this.name}:pollingComplete`, | ||
networkClientId, | ||
); | ||
|
||
/** | ||
* Stops polling for a networkClientId | ||
* | ||
* @param pollingToken - The polling token to stop polling for | ||
*/ | ||
stop(pollingToken: string) { | ||
if (!pollingToken) { | ||
throw new Error('pollingToken required'); | ||
} | ||
let found = false; | ||
this.#networkClientIdTokensMap.forEach((tokens, networkClientId) => { | ||
if (tokens.has(pollingToken)) { | ||
found = true; | ||
this.#networkClientIdTokensMap | ||
.get(networkClientId) | ||
?.delete(pollingToken); | ||
if (this.#networkClientIdTokensMap.get(networkClientId)?.size === 0) { | ||
clearTimeout(this.#intervalIds[networkClientId]); | ||
delete this.#intervalIds[networkClientId]; | ||
this.#networkClientIdTokensMap.delete(networkClientId); | ||
this.#callbacks.forEach((callback) => { | ||
callback(networkClientId); | ||
}); | ||
this.#callbacks.clear(); | ||
} | ||
} | ||
}); | ||
if (!found) { | ||
throw new Error('pollingToken not found'); | ||
} | ||
}); | ||
if (!found) { | ||
throw new Error('pollingToken not found'); | ||
} | ||
} | ||
|
||
/** | ||
* Executes the poll for a networkClientId | ||
* | ||
* @param networkClientId - The networkClientId to execute the poll for | ||
*/ | ||
abstract executePoll(networkClientId: NetworkClientId): Promise<void>; | ||
/** | ||
* Executes the poll for a networkClientId | ||
* | ||
* @param networkClientId - The networkClientId to execute the poll for | ||
*/ | ||
abstract executePoll(networkClientId: NetworkClientId): Promise<void>; | ||
|
||
#poll(networkClientId: NetworkClientId) { | ||
if (this.intervalIds[networkClientId]) { | ||
clearTimeout(this.intervalIds[networkClientId]); | ||
delete this.intervalIds[networkClientId]; | ||
} | ||
this.intervalIds[networkClientId] = setTimeout(async () => { | ||
try { | ||
await this.executePoll(networkClientId); | ||
} catch (error) { | ||
console.error(error); | ||
#poll(networkClientId: NetworkClientId) { | ||
if (this.#intervalIds[networkClientId]) { | ||
clearTimeout(this.#intervalIds[networkClientId]); | ||
delete this.#intervalIds[networkClientId]; | ||
} | ||
this.#poll(networkClientId); | ||
}, this.#intervalLength); | ||
this.#intervalIds[networkClientId] = setTimeout(async () => { | ||
try { | ||
await this.executePoll(networkClientId); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
this.#poll(networkClientId); | ||
}, this.#intervalLength); | ||
} | ||
|
||
onPollingComplete(callback: (networkClientId: NetworkClientId) => void) { | ||
this.#callbacks.add(callback); | ||
} | ||
} | ||
return PollingControllerBase; | ||
} | ||
|
||
export const PollingController = PollingControllerMixin(BaseControllerV2); | ||
export const PollingControllerV1 = PollingControllerMixin(BaseController); |