1
- import { BaseController , BaseControllerV1 } from '@metamask/base-controller' ;
2
1
import type {
3
2
ActionConstraint ,
4
3
BaseConfig ,
@@ -8,22 +7,34 @@ import type {
8
7
StateConstraint ,
9
8
StateMetadata ,
10
9
ControllerStateChangeEvent ,
10
+ Listener ,
11
11
} from '@metamask/base-controller' ;
12
+ import { BaseController , BaseControllerV1 } from '@metamask/base-controller' ;
12
13
import type { Patch } from 'immer' ;
13
14
14
15
export const controllerName = 'ComposableController' ;
15
16
17
+ type MessengerConsumerInstance = {
18
+ name : string ;
19
+ messagingSystem : RestrictedControllerMessengerConstraint ;
20
+ } ;
21
+
16
22
/**
17
23
* A universal subtype of all controller instances that extend from `BaseControllerV1`.
18
24
* Any `BaseControllerV1` instance can be assigned to this type.
19
25
*
20
26
* Note that this type is not the widest subtype or narrowest supertype of all `BaseControllerV1` instances.
21
27
* This type is therefore unsuitable for general use as a type constraint, and is only intended for use within the ComposableController.
22
28
*/
23
- export type BaseControllerV1Instance =
24
- // `any` is used so that all `BaseControllerV1` instances are assignable to this type.
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- BaseControllerV1 < any , any > ;
29
+ export type BaseControllerV1Instance = {
30
+ name : string ;
31
+ config : BaseConfig & object ;
32
+ defaultConfig : BaseConfig & object ;
33
+ state : BaseState & object ;
34
+ defaultState : BaseState & object ;
35
+ subscribe : ( listener : Listener < BaseState & object > ) => void ;
36
+ disabled : boolean ;
37
+ } & Partial < Pick < MessengerConsumerInstance , 'messagingSystem' > > ;
27
38
28
39
/**
29
40
* A universal subtype of all controller instances that extend from `BaseController` (formerly `BaseControllerV2`).
@@ -34,9 +45,12 @@ export type BaseControllerV1Instance =
34
45
*
35
46
* For this reason, we only look for `BaseController` properties that we use in the ComposableController (name and state).
36
47
*/
37
- export type BaseControllerInstance = {
48
+ export type BaseControllerInstance <
49
+ State extends StateConstraint = StateConstraint ,
50
+ > = {
38
51
name : string ;
39
- state : StateConstraint ;
52
+ state : State ;
53
+ metadata : Record < string , unknown > ;
40
54
} ;
41
55
42
56
/**
@@ -46,42 +60,63 @@ export type BaseControllerInstance = {
46
60
* Note that this type is not the widest subtype or narrowest supertype of all `BaseController` and `BaseControllerV1` instances.
47
61
* This type is therefore unsuitable for general use as a type constraint, and is only intended for use within the ComposableController.
48
62
*/
49
- export type ControllerInstance =
63
+ export type WalletComponentInstance =
50
64
| BaseControllerV1Instance
51
- | BaseControllerInstance ;
65
+ | BaseControllerInstance
66
+ | MessengerConsumerInstance ;
67
+
68
+ export type ControllerInstance = Exclude <
69
+ WalletComponentInstance ,
70
+ MessengerConsumerInstance
71
+ > ;
52
72
53
73
/**
54
74
* The narrowest supertype of all `RestrictedControllerMessenger` instances.
55
75
*/
56
- export type RestrictedControllerMessengerConstraint =
57
- RestrictedControllerMessenger <
58
- string ,
59
- ActionConstraint ,
60
- EventConstraint ,
61
- string ,
62
- string
63
- > ;
76
+ export type RestrictedControllerMessengerConstraint <
77
+ ControllerName extends string = string ,
78
+ > = RestrictedControllerMessenger <
79
+ ControllerName ,
80
+ ActionConstraint ,
81
+ EventConstraint ,
82
+ string ,
83
+ string
84
+ > ;
85
+
86
+ /**
87
+ * Determines if the given class has a messaging system.
88
+ * @param component - Component instance to check
89
+ * @returns True if the component is an instance of `MessengerConsumerInstance`
90
+ */
91
+ export function isMessengerConsumer (
92
+ component : WalletComponentInstance ,
93
+ ) : component is MessengerConsumerInstance {
94
+ return 'name' in component && 'messagingSystem' in component ;
95
+ }
64
96
65
97
/**
66
98
* Determines if the given controller is an instance of `BaseControllerV1`
67
99
* @param controller - Controller instance to check
68
100
* @returns True if the controller is an instance of `BaseControllerV1`
69
101
*/
70
102
export function isBaseControllerV1 (
71
- controller : ControllerInstance ,
72
- ) : controller is BaseControllerV1 <
73
- BaseConfig & Record < string , unknown > ,
74
- BaseState & Record < string , unknown >
75
- > {
103
+ controller : WalletComponentInstance ,
104
+ ) : controller is BaseControllerV1Instance {
76
105
return (
77
106
'name' in controller &&
78
107
typeof controller . name === 'string' &&
108
+ 'config' in controller &&
109
+ typeof controller . config === 'object' &&
79
110
'defaultConfig' in controller &&
80
111
typeof controller . defaultConfig === 'object' &&
112
+ 'state' in controller &&
113
+ typeof controller . state === 'object' &&
81
114
'defaultState' in controller &&
82
115
typeof controller . defaultState === 'object' &&
83
116
'disabled' in controller &&
84
117
typeof controller . disabled === 'boolean' &&
118
+ 'subscribe' in controller &&
119
+ typeof controller . subscribe === 'function' &&
85
120
controller instanceof BaseControllerV1
86
121
) ;
87
122
}
@@ -92,17 +127,15 @@ export function isBaseControllerV1(
92
127
* @returns True if the controller is an instance of `BaseController`
93
128
*/
94
129
export function isBaseController (
95
- controller : ControllerInstance ,
96
- ) : controller is BaseController <
97
- string ,
98
- StateConstraint ,
99
- RestrictedControllerMessengerConstraint
100
- > {
130
+ controller : WalletComponentInstance ,
131
+ ) : controller is BaseControllerInstance {
101
132
return (
102
133
'name' in controller &&
103
134
typeof controller . name === 'string' &&
104
135
'state' in controller &&
105
136
typeof controller . state === 'object' &&
137
+ 'metadata' in controller &&
138
+ typeof controller . metadata === 'object' &&
106
139
controller instanceof BaseController
107
140
) ;
108
141
}
@@ -186,25 +219,13 @@ export type ComposableControllerMessenger<
186
219
AllowedEvents < ComposableControllerState > [ 'type' ]
187
220
> ;
188
221
189
- type GetChildControllers <
190
- ComposableControllerState ,
191
- ControllerName extends keyof ComposableControllerState = keyof ComposableControllerState ,
192
- > = ControllerName extends string
193
- ? ComposableControllerState [ ControllerName ] extends StateConstraint
194
- ? { name : ControllerName ; state : ComposableControllerState [ ControllerName ] }
195
- : BaseControllerV1 <
196
- BaseConfig & Record < string , unknown > ,
197
- BaseState & ComposableControllerState [ ControllerName ]
198
- >
199
- : never ;
200
-
201
222
/**
202
223
* Controller that can be used to compose multiple controllers together.
203
224
* @template ChildControllerState - The composed state of the child controllers that are being used to instantiate the composable controller.
204
225
*/
205
226
export class ComposableController <
206
227
ComposableControllerState extends LegacyComposableControllerStateConstraint ,
207
- ChildControllers extends ControllerInstance = GetChildControllers < ComposableControllerState > ,
228
+ ChildControllers extends WalletComponentInstance ,
208
229
> extends BaseController <
209
230
typeof controllerName ,
210
231
ComposableControllerState ,
@@ -242,7 +263,9 @@ export class ComposableController<
242
263
) ,
243
264
state : controllers . reduce < ComposableControllerState > (
244
265
( state , controller ) => {
245
- return { ...state , [ controller . name ] : controller . state } ;
266
+ return 'state' in controller
267
+ ? { ...state , [ controller . name ] : controller . state }
268
+ : state ;
246
269
} ,
247
270
{ } as never ,
248
271
) ,
@@ -256,36 +279,32 @@ export class ComposableController<
256
279
257
280
/**
258
281
* Constructor helper that subscribes to child controller state changes.
259
- * @param controller - Controller instance to update
282
+ * @param component - Wallet component instance to update
260
283
*/
261
- #updateChildController( controller : ControllerInstance ) : void {
262
- if ( ! isBaseController ( controller ) && ! isBaseControllerV1 ( controller ) ) {
263
- throw new Error (
264
- 'Invalid controller: controller must extend from BaseController or BaseControllerV1' ,
265
- ) ;
266
- }
267
-
268
- const { name } = controller ;
284
+ #updateChildController( component : WalletComponentInstance ) : void {
285
+ const { name } = component ;
269
286
if (
270
- ( isBaseControllerV1 ( controller ) && 'messagingSystem' in controller ) ||
271
- isBaseController ( controller )
287
+ isBaseController ( component ) ||
288
+ ( isBaseControllerV1 ( component ) && isMessengerConsumer ( component ) )
272
289
) {
273
290
this . messagingSystem . subscribe (
274
- // TODO: Either fix this lint violation or explain why it's necessary to ignore.
275
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
276
291
`${ name } :stateChange` ,
277
- ( childState : Record < string , unknown > ) => {
292
+ ( childState : StateConstraint | ( BaseState & object ) ) => {
278
293
this . update ( ( state ) => {
279
294
Object . assign ( state , { [ name ] : childState } ) ;
280
295
} ) ;
281
296
} ,
282
297
) ;
283
- } else if ( isBaseControllerV1 ( controller ) ) {
284
- controller . subscribe ( ( childState ) => {
298
+ } else if ( isBaseControllerV1 ( component ) ) {
299
+ component . subscribe ( ( childState : BaseState & object ) => {
285
300
this . update ( ( state ) => {
286
301
Object . assign ( state , { [ name ] : childState } ) ;
287
302
} ) ;
288
303
} ) ;
304
+ } else if ( ! isMessengerConsumer ( component ) ) {
305
+ throw new Error (
306
+ 'Invalid component: component must be a MessengerConsumer or a controller inheriting from BaseControllerV1.' ,
307
+ ) ;
289
308
}
290
309
}
291
310
}
0 commit comments