@metamask-previews/client-controller 0.0.0-preview-a196307b6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +180 -0
  4. package/dist/ClientController-method-action-types.cjs +7 -0
  5. package/dist/ClientController-method-action-types.cjs.map +1 -0
  6. package/dist/ClientController-method-action-types.d.cts +23 -0
  7. package/dist/ClientController-method-action-types.d.cts.map +1 -0
  8. package/dist/ClientController-method-action-types.d.mts +23 -0
  9. package/dist/ClientController-method-action-types.d.mts.map +1 -0
  10. package/dist/ClientController-method-action-types.mjs +6 -0
  11. package/dist/ClientController-method-action-types.mjs.map +1 -0
  12. package/dist/ClientController.cjs +117 -0
  13. package/dist/ClientController.cjs.map +1 -0
  14. package/dist/ClientController.d.cts +135 -0
  15. package/dist/ClientController.d.cts.map +1 -0
  16. package/dist/ClientController.d.mts +135 -0
  17. package/dist/ClientController.d.mts.map +1 -0
  18. package/dist/ClientController.mjs +112 -0
  19. package/dist/ClientController.mjs.map +1 -0
  20. package/dist/index.cjs +9 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +5 -0
  23. package/dist/index.d.cts.map +1 -0
  24. package/dist/index.d.mts +5 -0
  25. package/dist/index.d.mts.map +1 -0
  26. package/dist/index.mjs +3 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/selectors.cjs +18 -0
  29. package/dist/selectors.cjs.map +1 -0
  30. package/dist/selectors.d.cts +9 -0
  31. package/dist/selectors.d.cts.map +1 -0
  32. package/dist/selectors.d.mts +9 -0
  33. package/dist/selectors.d.mts.map +1 -0
  34. package/dist/selectors.mjs +15 -0
  35. package/dist/selectors.mjs.map +1 -0
  36. package/package.json +73 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Initial release of `@metamask/client-controller` ([#7808](https://github.com/MetaMask/core/pull/7808))
13
+ - `ClientController` for managing client (UI) open/closed state
14
+ - `ClientController:setUiOpen` messenger action for platform code to call
15
+ - `ClientController:stateChange` event for controllers to subscribe to lifecycle changes
16
+ - `isUiOpen` state property (not persisted - always starts as `false`)
17
+ - `clientControllerSelectors.selectIsUiOpen` selector for derived state access
18
+ - Full TypeScript support with exported types
19
+
20
+ [Unreleased]: https://github.com/MetaMask/core/
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MetaMask
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # `@metamask/client-controller`
2
+
3
+ Client-level state for MetaMask (e.g. whether a UI window is open). Provides a centralized way for controllers to respond to application lifecycle changes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @metamask/client-controller
9
+ ```
10
+
11
+ or
12
+
13
+ ```bash
14
+ npm install @metamask/client-controller
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Basic Setup
20
+
21
+ ```typescript
22
+ import { Messenger } from '@metamask/messenger';
23
+ import {
24
+ ClientController,
25
+ ClientControllerActions,
26
+ ClientControllerEvents,
27
+ } from '@metamask/client-controller';
28
+
29
+ const rootMessenger = new Messenger<
30
+ 'Root',
31
+ ClientControllerActions,
32
+ ClientControllerEvents
33
+ >({ namespace: 'Root' });
34
+
35
+ const controllerMessenger = new Messenger({
36
+ namespace: 'ClientController',
37
+ parent: rootMessenger,
38
+ });
39
+
40
+ const clientController = new ClientController({
41
+ messenger: controllerMessenger,
42
+ });
43
+ ```
44
+
45
+ ### Platform Integration
46
+
47
+ Platform code calls `ClientController:setUiOpen` when the UI is opened or
48
+ closed:
49
+
50
+ ```text
51
+ onUiOpened() {
52
+ controllerMessenger.call('ClientController:setUiOpen', true);
53
+ }
54
+
55
+ onUiClosed() {
56
+ controllerMessenger.call('ClientController:setUiOpen', false);
57
+ }
58
+ ```
59
+
60
+ ### Consumer controller and using with other lifecycle state (e.g. Keyring unlock/lock)
61
+
62
+ Use `ClientController:stateChange` only for behavior that **must** run when the
63
+ UI is open or closed (e.g., pausing/resuming a critical background task). **Use
64
+ the selector** when subscribing so the handler receives a single derived value
65
+ (e.g. `isUiOpen`), and **prefer pause/resume** over stop/start for polling.
66
+
67
+ UI open/close alone is usually not enough to decide when to start or stop work.
68
+ Combine `ClientController:stateChange` with other lifecycle events, such as
69
+ **KeyringController:unlock** / **KeyringController:lock** (or any controller that
70
+ expresses "ready for background work"). Only start subscriptions, polling, or
71
+ network requests when **both** the UI is open and the keyring (or equivalent) is
72
+ unlocked; stop or pause when the UI closes **or** the keyring locks.
73
+
74
+ #### Important: Usage guidelines and warnings
75
+
76
+ **Do not subscribe to updates for all kinds of data as soon as the client
77
+ opens.** When MetaMask opens, the current screen may not need every type of
78
+ data. Starting subscriptions, polling, or network requests for everything when
79
+ `isUiOpen` becomes true can lead to unnecessary network traffic and battery
80
+ use, requests before onboarding is complete (a recurring source of issues), and
81
+ poor performance as more features are added.
82
+
83
+ **Use this controller responsibly:**
84
+
85
+ - Start only the subscriptions, polling, or requests that are **needed for the
86
+ current screen or flow**
87
+ - Do **not** start network-dependent or heavy behavior solely because
88
+ `ClientController:stateChange` reported `isUiOpen: true`
89
+ - Consider **deferring** non-critical updates until the user has completed
90
+ onboarding or reached a screen that needs that data
91
+ - Prefer starting and stopping per feature or per screen (e.g., when a
92
+ component mounts that needs the data) rather than globally when the client
93
+ opens
94
+ - **Combine with Keyring unlock/lock:** Only start work when it is appropriate
95
+ for both UI open state and wallet state (e.g. client open **and** keyring
96
+ unlocked)
97
+ - **Prefer pause/resume over stop/start for polling** so you can resume without
98
+ full re-initialization. Use the selector when subscribing (see example
99
+ below).
100
+
101
+ ```typescript
102
+ import { clientControllerSelectors } from '@metamask/client-controller';
103
+
104
+ class SomeDataController extends BaseController {
105
+ #uiOpen = false;
106
+ #keyringUnlocked = false;
107
+
108
+ constructor({ messenger }) {
109
+ super({ messenger, ... });
110
+
111
+ messenger.subscribe(
112
+ 'ClientController:stateChange',
113
+ (isUiOpen) => {
114
+ this.#uiOpen = isUiOpen;
115
+ this.updateActive();
116
+ },
117
+ clientControllerSelectors.selectIsUiOpen,
118
+ );
119
+
120
+ messenger.subscribe('KeyringController:unlock', () => {
121
+ this.#keyringUnlocked = true;
122
+ this.updateActive();
123
+ });
124
+
125
+ messenger.subscribe('KeyringController:lock', () => {
126
+ this.#keyringUnlocked = false;
127
+ this.updateActive();
128
+ });
129
+ }
130
+
131
+ updateActive() {
132
+ const shouldRun = this.#uiOpen && this.#keyringUnlocked;
133
+ if (shouldRun) {
134
+ this.resume();
135
+ } else {
136
+ this.pause();
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Note: `stateChange` emits `[state, patches]`; the selector receives the full
143
+ payload and returns the value passed to the handler (here, `isUiOpen`).
144
+
145
+ ## API Reference
146
+
147
+ ### State
148
+
149
+ | Property | Type | Description |
150
+ | ---------- | --------- | ------------------------------------------ |
151
+ | `isUiOpen` | `boolean` | Whether the client (UI) is currently open. |
152
+
153
+ State is not persisted. It always starts as `false`.
154
+
155
+ ### Actions
156
+
157
+ | Action | Parameters | Description |
158
+ | ---------------------------- | --------------- | ---------------------------- |
159
+ | `ClientController:getState` | none | Returns current state. |
160
+ | `ClientController:setUiOpen` | `open: boolean` | Sets whether the UI is open. |
161
+
162
+ ### Events
163
+
164
+ | Event | Payload | Description |
165
+ | ------------------------------ | ------------------ | ---------------------------- |
166
+ | `ClientController:stateChange` | `[state, patches]` | Standard state change event. |
167
+
168
+ ### Selectors
169
+
170
+ ```typescript
171
+ import { clientControllerSelectors } from '@metamask/client-controller';
172
+
173
+ const state = messenger.call('ClientController:getState');
174
+ const isOpen = clientControllerSelectors.selectIsUiOpen(state);
175
+ ```
176
+
177
+ ## Contributing
178
+
179
+ This package is part of a monorepo. Instructions for contributing can be found
180
+ in the [monorepo README](https://github.com/MetaMask/core#readme).
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
4
+ * Do not edit manually.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=ClientController-method-action-types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController-method-action-types.cjs","sourceRoot":"","sources":["../src/ClientController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { ClientController } from './ClientController';\n\n/**\n * Updates state with whether the MetaMask UI is open.\n *\n * This method should be called when the user has opened the first window or\n * screen containing the MetaMask UI, or closed the last window or screen\n * containing the MetaMask UI.\n *\n * @param open - Whether the MetaMask UI is open.\n */\nexport type ClientControllerSetUiOpenAction = {\n type: `ClientController:setUiOpen`;\n handler: ClientController['setUiOpen'];\n};\n\n/**\n * Union of all ClientController action types.\n */\nexport type ClientControllerMethodActions = ClientControllerSetUiOpenAction;\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ import type { ClientController } from "./ClientController.cjs";
6
+ /**
7
+ * Updates state with whether the MetaMask UI is open.
8
+ *
9
+ * This method should be called when the user has opened the first window or
10
+ * screen containing the MetaMask UI, or closed the last window or screen
11
+ * containing the MetaMask UI.
12
+ *
13
+ * @param open - Whether the MetaMask UI is open.
14
+ */
15
+ export type ClientControllerSetUiOpenAction = {
16
+ type: `ClientController:setUiOpen`;
17
+ handler: ClientController['setUiOpen'];
18
+ };
19
+ /**
20
+ * Union of all ClientController action types.
21
+ */
22
+ export type ClientControllerMethodActions = ClientControllerSetUiOpenAction;
23
+ //# sourceMappingURL=ClientController-method-action-types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController-method-action-types.d.cts","sourceRoot":"","sources":["../src/ClientController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA2B;AAE3D;;;;;;;;GAQG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,4BAA4B,CAAC;IACnC,OAAO,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG,+BAA+B,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ import type { ClientController } from "./ClientController.mjs";
6
+ /**
7
+ * Updates state with whether the MetaMask UI is open.
8
+ *
9
+ * This method should be called when the user has opened the first window or
10
+ * screen containing the MetaMask UI, or closed the last window or screen
11
+ * containing the MetaMask UI.
12
+ *
13
+ * @param open - Whether the MetaMask UI is open.
14
+ */
15
+ export type ClientControllerSetUiOpenAction = {
16
+ type: `ClientController:setUiOpen`;
17
+ handler: ClientController['setUiOpen'];
18
+ };
19
+ /**
20
+ * Union of all ClientController action types.
21
+ */
22
+ export type ClientControllerMethodActions = ClientControllerSetUiOpenAction;
23
+ //# sourceMappingURL=ClientController-method-action-types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController-method-action-types.d.mts","sourceRoot":"","sources":["../src/ClientController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA2B;AAE3D;;;;;;;;GAQG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,4BAA4B,CAAC;IACnC,OAAO,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG,+BAA+B,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=ClientController-method-action-types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController-method-action-types.mjs","sourceRoot":"","sources":["../src/ClientController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { ClientController } from './ClientController';\n\n/**\n * Updates state with whether the MetaMask UI is open.\n *\n * This method should be called when the user has opened the first window or\n * screen containing the MetaMask UI, or closed the last window or screen\n * containing the MetaMask UI.\n *\n * @param open - Whether the MetaMask UI is open.\n */\nexport type ClientControllerSetUiOpenAction = {\n type: `ClientController:setUiOpen`;\n handler: ClientController['setUiOpen'];\n};\n\n/**\n * Union of all ClientController action types.\n */\nexport type ClientControllerMethodActions = ClientControllerSetUiOpenAction;\n"]}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientController = exports.getDefaultClientControllerState = exports.controllerName = void 0;
4
+ const base_controller_1 = require("@metamask/base-controller");
5
+ // === GENERAL ===
6
+ /**
7
+ * The name of the {@link ClientController}.
8
+ */
9
+ exports.controllerName = 'ClientController';
10
+ /**
11
+ * Constructs the default {@link ClientController} state.
12
+ *
13
+ * @returns The default {@link ClientController} state.
14
+ */
15
+ function getDefaultClientControllerState() {
16
+ return {
17
+ isUiOpen: false,
18
+ };
19
+ }
20
+ exports.getDefaultClientControllerState = getDefaultClientControllerState;
21
+ /**
22
+ * The metadata for each property in {@link ClientControllerState}.
23
+ */
24
+ const controllerMetadata = {
25
+ isUiOpen: {
26
+ includeInDebugSnapshot: true,
27
+ includeInStateLogs: true,
28
+ persist: false,
29
+ usedInUi: false,
30
+ },
31
+ };
32
+ // === MESSENGER ===
33
+ const MESSENGER_EXPOSED_METHODS = ['setUiOpen'];
34
+ /**
35
+ * `ClientController` manages the application lifecycle state.
36
+ *
37
+ * This controller tracks whether the MetaMask UI is open and publishes state
38
+ * change events that other controllers can subscribe to for adjusting their behavior.
39
+ *
40
+ * **Use cases:**
41
+ * - Polling controllers can pause when the UI closes, resume when it opens
42
+ * - WebSocket connections can disconnect when closed, reconnect when opened
43
+ * - Real-time subscriptions can pause when not visible
44
+ *
45
+ * **Platform Integration:**
46
+ * Platform code should call `ClientController:setUiOpen` via messenger.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // In MetamaskController or platform code
51
+ * onUiOpened() {
52
+ * // ...
53
+ * this.controllerMessenger.call('ClientController:setUiOpen', true);
54
+ * }
55
+ *
56
+ * onUiClosed() {
57
+ * // ...
58
+ * this.controllerMessenger.call('ClientController:setUiOpen', false);
59
+ * }
60
+ *
61
+ * // Consumer controller subscribing to state changes
62
+ * class MyController extends BaseController {
63
+ * constructor({ messenger }) {
64
+ * super({ messenger, ... });
65
+ *
66
+ * messenger.subscribe(
67
+ * 'ClientController:stateChange',
68
+ * (isClientOpen) => {
69
+ * if (isClientOpen) {
70
+ * this.resumePolling();
71
+ * } else {
72
+ * this.pausePolling();
73
+ * }
74
+ * },
75
+ * clientControllerSelectors.selectIsUiOpen,
76
+ * );
77
+ * }
78
+ * }
79
+ * ```
80
+ */
81
+ class ClientController extends base_controller_1.BaseController {
82
+ /**
83
+ * Constructs a new {@link ClientController}.
84
+ *
85
+ * @param options - The constructor options.
86
+ * @param options.messenger - The messenger suited for this controller.
87
+ * @param options.state - The initial state to set on this controller.
88
+ */
89
+ constructor({ messenger, state = {} }) {
90
+ super({
91
+ messenger,
92
+ metadata: controllerMetadata,
93
+ name: exports.controllerName,
94
+ state: {
95
+ ...getDefaultClientControllerState(),
96
+ ...state,
97
+ },
98
+ });
99
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
100
+ }
101
+ /**
102
+ * Updates state with whether the MetaMask UI is open.
103
+ *
104
+ * This method should be called when the user has opened the first window or
105
+ * screen containing the MetaMask UI, or closed the last window or screen
106
+ * containing the MetaMask UI.
107
+ *
108
+ * @param open - Whether the MetaMask UI is open.
109
+ */
110
+ setUiOpen(open) {
111
+ this.update((state) => {
112
+ state.isUiOpen = open;
113
+ });
114
+ }
115
+ }
116
+ exports.ClientController = ClientController;
117
+ //# sourceMappingURL=ClientController.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController.cjs","sourceRoot":"","sources":["../src/ClientController.ts"],"names":[],"mappings":";;;AAKA,+DAA2D;AAK3D,kBAAkB;AAElB;;GAEG;AACU,QAAA,cAAc,GAAG,kBAAkB,CAAC;AAgBjD;;;;GAIG;AACH,SAAgB,+BAA+B;IAC7C,OAAO;QACL,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC;AAJD,0EAIC;AAED;;GAEG;AACH,MAAM,kBAAkB,GAAG;IACzB,QAAQ,EAAE;QACR,sBAAsB,EAAE,IAAI;QAC5B,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK;KAChB;CAC6C,CAAC;AAEjD,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG,CAAC,WAAW,CAAU,CAAC;AAiEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,gBAAiB,SAAQ,gCAIrC;IACC;;;;;;OAMG;IACH,YAAY,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,EAA2B;QAC5D,KAAK,CAAC;YACJ,SAAS;YACT,QAAQ,EAAE,kBAAkB;YAC5B,IAAI,EAAE,sBAAc;YACpB,KAAK,EAAE;gBACL,GAAG,+BAA+B,EAAE;gBACpC,GAAG,KAAK;aACT;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAa;QACrB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA3CD,4CA2CC","sourcesContent":["import type {\n StateMetadata,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { ClientControllerMethodActions } from './ClientController-method-action-types';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ClientController}.\n */\nexport const controllerName = 'ClientController';\n\n// === STATE ===\n\n/**\n * Describes the shape of the state object for {@link ClientController}.\n */\nexport type ClientControllerState = {\n /**\n * Whether the user has opened at least one window or screen\n * containing the MetaMask UI. These windows or screens may or\n * may not be in an inactive state.\n */\n isUiOpen: boolean;\n};\n\n/**\n * Constructs the default {@link ClientController} state.\n *\n * @returns The default {@link ClientController} state.\n */\nexport function getDefaultClientControllerState(): ClientControllerState {\n return {\n isUiOpen: false,\n };\n}\n\n/**\n * The metadata for each property in {@link ClientControllerState}.\n */\nconst controllerMetadata = {\n isUiOpen: {\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n persist: false,\n usedInUi: false,\n },\n} satisfies StateMetadata<ClientControllerState>;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['setUiOpen'] as const;\n\n/**\n * Retrieves the state of the {@link ClientController}.\n */\nexport type ClientControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ClientControllerState\n>;\n\n/**\n * Actions that {@link ClientController} exposes.\n */\nexport type ClientControllerActions =\n | ClientControllerGetStateAction\n | ClientControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link ClientController} calls.\n */\ntype AllowedActions = never;\n\n/**\n * Published when the state of {@link ClientController} changes.\n */\nexport type ClientControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n ClientControllerState\n>;\n\n/**\n * Events that {@link ClientController} exposes.\n */\nexport type ClientControllerEvents = ClientControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ClientController} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger for {@link ClientController}.\n */\nexport type ClientControllerMessenger = Messenger<\n typeof controllerName,\n ClientControllerActions | AllowedActions,\n ClientControllerEvents | AllowedEvents\n>;\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * The options for constructing a {@link ClientController}.\n */\nexport type ClientControllerOptions = {\n /**\n * The messenger suited for this controller.\n */\n messenger: ClientControllerMessenger;\n /**\n * The initial state to set on this controller.\n */\n state?: Partial<ClientControllerState>;\n};\n\n/**\n * `ClientController` manages the application lifecycle state.\n *\n * This controller tracks whether the MetaMask UI is open and publishes state\n * change events that other controllers can subscribe to for adjusting their behavior.\n *\n * **Use cases:**\n * - Polling controllers can pause when the UI closes, resume when it opens\n * - WebSocket connections can disconnect when closed, reconnect when opened\n * - Real-time subscriptions can pause when not visible\n *\n * **Platform Integration:**\n * Platform code should call `ClientController:setUiOpen` via messenger.\n *\n * @example\n * ```typescript\n * // In MetamaskController or platform code\n * onUiOpened() {\n * // ...\n * this.controllerMessenger.call('ClientController:setUiOpen', true);\n * }\n *\n * onUiClosed() {\n * // ...\n * this.controllerMessenger.call('ClientController:setUiOpen', false);\n * }\n *\n * // Consumer controller subscribing to state changes\n * class MyController extends BaseController {\n * constructor({ messenger }) {\n * super({ messenger, ... });\n *\n * messenger.subscribe(\n * 'ClientController:stateChange',\n * (isClientOpen) => {\n * if (isClientOpen) {\n * this.resumePolling();\n * } else {\n * this.pausePolling();\n * }\n * },\n * clientControllerSelectors.selectIsUiOpen,\n * );\n * }\n * }\n * ```\n */\nexport class ClientController extends BaseController<\n typeof controllerName,\n ClientControllerState,\n ClientControllerMessenger\n> {\n /**\n * Constructs a new {@link ClientController}.\n *\n * @param options - The constructor options.\n * @param options.messenger - The messenger suited for this controller.\n * @param options.state - The initial state to set on this controller.\n */\n constructor({ messenger, state = {} }: ClientControllerOptions) {\n super({\n messenger,\n metadata: controllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultClientControllerState(),\n ...state,\n },\n });\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Updates state with whether the MetaMask UI is open.\n *\n * This method should be called when the user has opened the first window or\n * screen containing the MetaMask UI, or closed the last window or screen\n * containing the MetaMask UI.\n *\n * @param open - Whether the MetaMask UI is open.\n */\n setUiOpen(open: boolean): void {\n this.update((state) => {\n state.isUiOpen = open;\n });\n }\n}\n"]}
@@ -0,0 +1,135 @@
1
+ import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
2
+ import { BaseController } from "@metamask/base-controller";
3
+ import type { Messenger } from "@metamask/messenger";
4
+ import type { ClientControllerMethodActions } from "./ClientController-method-action-types.cjs";
5
+ /**
6
+ * The name of the {@link ClientController}.
7
+ */
8
+ export declare const controllerName = "ClientController";
9
+ /**
10
+ * Describes the shape of the state object for {@link ClientController}.
11
+ */
12
+ export type ClientControllerState = {
13
+ /**
14
+ * Whether the user has opened at least one window or screen
15
+ * containing the MetaMask UI. These windows or screens may or
16
+ * may not be in an inactive state.
17
+ */
18
+ isUiOpen: boolean;
19
+ };
20
+ /**
21
+ * Constructs the default {@link ClientController} state.
22
+ *
23
+ * @returns The default {@link ClientController} state.
24
+ */
25
+ export declare function getDefaultClientControllerState(): ClientControllerState;
26
+ /**
27
+ * Retrieves the state of the {@link ClientController}.
28
+ */
29
+ export type ClientControllerGetStateAction = ControllerGetStateAction<typeof controllerName, ClientControllerState>;
30
+ /**
31
+ * Actions that {@link ClientController} exposes.
32
+ */
33
+ export type ClientControllerActions = ClientControllerGetStateAction | ClientControllerMethodActions;
34
+ /**
35
+ * Actions from other messengers that {@link ClientController} calls.
36
+ */
37
+ type AllowedActions = never;
38
+ /**
39
+ * Published when the state of {@link ClientController} changes.
40
+ */
41
+ export type ClientControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, ClientControllerState>;
42
+ /**
43
+ * Events that {@link ClientController} exposes.
44
+ */
45
+ export type ClientControllerEvents = ClientControllerStateChangeEvent;
46
+ /**
47
+ * Events from other messengers that {@link ClientController} subscribes to.
48
+ */
49
+ type AllowedEvents = never;
50
+ /**
51
+ * The messenger for {@link ClientController}.
52
+ */
53
+ export type ClientControllerMessenger = Messenger<typeof controllerName, ClientControllerActions | AllowedActions, ClientControllerEvents | AllowedEvents>;
54
+ /**
55
+ * The options for constructing a {@link ClientController}.
56
+ */
57
+ export type ClientControllerOptions = {
58
+ /**
59
+ * The messenger suited for this controller.
60
+ */
61
+ messenger: ClientControllerMessenger;
62
+ /**
63
+ * The initial state to set on this controller.
64
+ */
65
+ state?: Partial<ClientControllerState>;
66
+ };
67
+ /**
68
+ * `ClientController` manages the application lifecycle state.
69
+ *
70
+ * This controller tracks whether the MetaMask UI is open and publishes state
71
+ * change events that other controllers can subscribe to for adjusting their behavior.
72
+ *
73
+ * **Use cases:**
74
+ * - Polling controllers can pause when the UI closes, resume when it opens
75
+ * - WebSocket connections can disconnect when closed, reconnect when opened
76
+ * - Real-time subscriptions can pause when not visible
77
+ *
78
+ * **Platform Integration:**
79
+ * Platform code should call `ClientController:setUiOpen` via messenger.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // In MetamaskController or platform code
84
+ * onUiOpened() {
85
+ * // ...
86
+ * this.controllerMessenger.call('ClientController:setUiOpen', true);
87
+ * }
88
+ *
89
+ * onUiClosed() {
90
+ * // ...
91
+ * this.controllerMessenger.call('ClientController:setUiOpen', false);
92
+ * }
93
+ *
94
+ * // Consumer controller subscribing to state changes
95
+ * class MyController extends BaseController {
96
+ * constructor({ messenger }) {
97
+ * super({ messenger, ... });
98
+ *
99
+ * messenger.subscribe(
100
+ * 'ClientController:stateChange',
101
+ * (isClientOpen) => {
102
+ * if (isClientOpen) {
103
+ * this.resumePolling();
104
+ * } else {
105
+ * this.pausePolling();
106
+ * }
107
+ * },
108
+ * clientControllerSelectors.selectIsUiOpen,
109
+ * );
110
+ * }
111
+ * }
112
+ * ```
113
+ */
114
+ export declare class ClientController extends BaseController<typeof controllerName, ClientControllerState, ClientControllerMessenger> {
115
+ /**
116
+ * Constructs a new {@link ClientController}.
117
+ *
118
+ * @param options - The constructor options.
119
+ * @param options.messenger - The messenger suited for this controller.
120
+ * @param options.state - The initial state to set on this controller.
121
+ */
122
+ constructor({ messenger, state }: ClientControllerOptions);
123
+ /**
124
+ * Updates state with whether the MetaMask UI is open.
125
+ *
126
+ * This method should be called when the user has opened the first window or
127
+ * screen containing the MetaMask UI, or closed the last window or screen
128
+ * containing the MetaMask UI.
129
+ *
130
+ * @param open - Whether the MetaMask UI is open.
131
+ */
132
+ setUiOpen(open: boolean): void;
133
+ }
134
+ export {};
135
+ //# sourceMappingURL=ClientController.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController.d.cts","sourceRoot":"","sources":["../src/ClientController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,6BAA6B,EAAE,mDAA+C;AAI5F;;GAEG;AACH,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAIjD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,+BAA+B,IAAI,qBAAqB,CAIvE;AAkBD;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,wBAAwB,CACnE,OAAO,cAAc,EACrB,qBAAqB,CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAC/B,8BAA8B,GAC9B,6BAA6B,CAAC;AAElC;;GAEG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG,0BAA0B,CACvE,OAAO,cAAc,EACrB,qBAAqB,CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AAEtE;;GAEG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,CAC/C,OAAO,cAAc,EACrB,uBAAuB,GAAG,cAAc,EACxC,sBAAsB,GAAG,aAAa,CACvC,CAAC;AAIF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,SAAS,EAAE,yBAAyB,CAAC;IACrC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,gBAAiB,SAAQ,cAAc,CAClD,OAAO,cAAc,EACrB,qBAAqB,EACrB,yBAAyB,CAC1B;IACC;;;;;;OAMG;gBACS,EAAE,SAAS,EAAE,KAAU,EAAE,EAAE,uBAAuB;IAiB9D;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CAK/B"}
@@ -0,0 +1,135 @@
1
+ import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
2
+ import { BaseController } from "@metamask/base-controller";
3
+ import type { Messenger } from "@metamask/messenger";
4
+ import type { ClientControllerMethodActions } from "./ClientController-method-action-types.mjs";
5
+ /**
6
+ * The name of the {@link ClientController}.
7
+ */
8
+ export declare const controllerName = "ClientController";
9
+ /**
10
+ * Describes the shape of the state object for {@link ClientController}.
11
+ */
12
+ export type ClientControllerState = {
13
+ /**
14
+ * Whether the user has opened at least one window or screen
15
+ * containing the MetaMask UI. These windows or screens may or
16
+ * may not be in an inactive state.
17
+ */
18
+ isUiOpen: boolean;
19
+ };
20
+ /**
21
+ * Constructs the default {@link ClientController} state.
22
+ *
23
+ * @returns The default {@link ClientController} state.
24
+ */
25
+ export declare function getDefaultClientControllerState(): ClientControllerState;
26
+ /**
27
+ * Retrieves the state of the {@link ClientController}.
28
+ */
29
+ export type ClientControllerGetStateAction = ControllerGetStateAction<typeof controllerName, ClientControllerState>;
30
+ /**
31
+ * Actions that {@link ClientController} exposes.
32
+ */
33
+ export type ClientControllerActions = ClientControllerGetStateAction | ClientControllerMethodActions;
34
+ /**
35
+ * Actions from other messengers that {@link ClientController} calls.
36
+ */
37
+ type AllowedActions = never;
38
+ /**
39
+ * Published when the state of {@link ClientController} changes.
40
+ */
41
+ export type ClientControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, ClientControllerState>;
42
+ /**
43
+ * Events that {@link ClientController} exposes.
44
+ */
45
+ export type ClientControllerEvents = ClientControllerStateChangeEvent;
46
+ /**
47
+ * Events from other messengers that {@link ClientController} subscribes to.
48
+ */
49
+ type AllowedEvents = never;
50
+ /**
51
+ * The messenger for {@link ClientController}.
52
+ */
53
+ export type ClientControllerMessenger = Messenger<typeof controllerName, ClientControllerActions | AllowedActions, ClientControllerEvents | AllowedEvents>;
54
+ /**
55
+ * The options for constructing a {@link ClientController}.
56
+ */
57
+ export type ClientControllerOptions = {
58
+ /**
59
+ * The messenger suited for this controller.
60
+ */
61
+ messenger: ClientControllerMessenger;
62
+ /**
63
+ * The initial state to set on this controller.
64
+ */
65
+ state?: Partial<ClientControllerState>;
66
+ };
67
+ /**
68
+ * `ClientController` manages the application lifecycle state.
69
+ *
70
+ * This controller tracks whether the MetaMask UI is open and publishes state
71
+ * change events that other controllers can subscribe to for adjusting their behavior.
72
+ *
73
+ * **Use cases:**
74
+ * - Polling controllers can pause when the UI closes, resume when it opens
75
+ * - WebSocket connections can disconnect when closed, reconnect when opened
76
+ * - Real-time subscriptions can pause when not visible
77
+ *
78
+ * **Platform Integration:**
79
+ * Platform code should call `ClientController:setUiOpen` via messenger.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // In MetamaskController or platform code
84
+ * onUiOpened() {
85
+ * // ...
86
+ * this.controllerMessenger.call('ClientController:setUiOpen', true);
87
+ * }
88
+ *
89
+ * onUiClosed() {
90
+ * // ...
91
+ * this.controllerMessenger.call('ClientController:setUiOpen', false);
92
+ * }
93
+ *
94
+ * // Consumer controller subscribing to state changes
95
+ * class MyController extends BaseController {
96
+ * constructor({ messenger }) {
97
+ * super({ messenger, ... });
98
+ *
99
+ * messenger.subscribe(
100
+ * 'ClientController:stateChange',
101
+ * (isClientOpen) => {
102
+ * if (isClientOpen) {
103
+ * this.resumePolling();
104
+ * } else {
105
+ * this.pausePolling();
106
+ * }
107
+ * },
108
+ * clientControllerSelectors.selectIsUiOpen,
109
+ * );
110
+ * }
111
+ * }
112
+ * ```
113
+ */
114
+ export declare class ClientController extends BaseController<typeof controllerName, ClientControllerState, ClientControllerMessenger> {
115
+ /**
116
+ * Constructs a new {@link ClientController}.
117
+ *
118
+ * @param options - The constructor options.
119
+ * @param options.messenger - The messenger suited for this controller.
120
+ * @param options.state - The initial state to set on this controller.
121
+ */
122
+ constructor({ messenger, state }: ClientControllerOptions);
123
+ /**
124
+ * Updates state with whether the MetaMask UI is open.
125
+ *
126
+ * This method should be called when the user has opened the first window or
127
+ * screen containing the MetaMask UI, or closed the last window or screen
128
+ * containing the MetaMask UI.
129
+ *
130
+ * @param open - Whether the MetaMask UI is open.
131
+ */
132
+ setUiOpen(open: boolean): void;
133
+ }
134
+ export {};
135
+ //# sourceMappingURL=ClientController.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController.d.mts","sourceRoot":"","sources":["../src/ClientController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,6BAA6B,EAAE,mDAA+C;AAI5F;;GAEG;AACH,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAIjD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,+BAA+B,IAAI,qBAAqB,CAIvE;AAkBD;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,wBAAwB,CACnE,OAAO,cAAc,EACrB,qBAAqB,CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAC/B,8BAA8B,GAC9B,6BAA6B,CAAC;AAElC;;GAEG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG,0BAA0B,CACvE,OAAO,cAAc,EACrB,qBAAqB,CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AAEtE;;GAEG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,CAC/C,OAAO,cAAc,EACrB,uBAAuB,GAAG,cAAc,EACxC,sBAAsB,GAAG,aAAa,CACvC,CAAC;AAIF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,SAAS,EAAE,yBAAyB,CAAC;IACrC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,gBAAiB,SAAQ,cAAc,CAClD,OAAO,cAAc,EACrB,qBAAqB,EACrB,yBAAyB,CAC1B;IACC;;;;;;OAMG;gBACS,EAAE,SAAS,EAAE,KAAU,EAAE,EAAE,uBAAuB;IAiB9D;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CAK/B"}
@@ -0,0 +1,112 @@
1
+ import { BaseController } from "@metamask/base-controller";
2
+ // === GENERAL ===
3
+ /**
4
+ * The name of the {@link ClientController}.
5
+ */
6
+ export const controllerName = 'ClientController';
7
+ /**
8
+ * Constructs the default {@link ClientController} state.
9
+ *
10
+ * @returns The default {@link ClientController} state.
11
+ */
12
+ export function getDefaultClientControllerState() {
13
+ return {
14
+ isUiOpen: false,
15
+ };
16
+ }
17
+ /**
18
+ * The metadata for each property in {@link ClientControllerState}.
19
+ */
20
+ const controllerMetadata = {
21
+ isUiOpen: {
22
+ includeInDebugSnapshot: true,
23
+ includeInStateLogs: true,
24
+ persist: false,
25
+ usedInUi: false,
26
+ },
27
+ };
28
+ // === MESSENGER ===
29
+ const MESSENGER_EXPOSED_METHODS = ['setUiOpen'];
30
+ /**
31
+ * `ClientController` manages the application lifecycle state.
32
+ *
33
+ * This controller tracks whether the MetaMask UI is open and publishes state
34
+ * change events that other controllers can subscribe to for adjusting their behavior.
35
+ *
36
+ * **Use cases:**
37
+ * - Polling controllers can pause when the UI closes, resume when it opens
38
+ * - WebSocket connections can disconnect when closed, reconnect when opened
39
+ * - Real-time subscriptions can pause when not visible
40
+ *
41
+ * **Platform Integration:**
42
+ * Platform code should call `ClientController:setUiOpen` via messenger.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // In MetamaskController or platform code
47
+ * onUiOpened() {
48
+ * // ...
49
+ * this.controllerMessenger.call('ClientController:setUiOpen', true);
50
+ * }
51
+ *
52
+ * onUiClosed() {
53
+ * // ...
54
+ * this.controllerMessenger.call('ClientController:setUiOpen', false);
55
+ * }
56
+ *
57
+ * // Consumer controller subscribing to state changes
58
+ * class MyController extends BaseController {
59
+ * constructor({ messenger }) {
60
+ * super({ messenger, ... });
61
+ *
62
+ * messenger.subscribe(
63
+ * 'ClientController:stateChange',
64
+ * (isClientOpen) => {
65
+ * if (isClientOpen) {
66
+ * this.resumePolling();
67
+ * } else {
68
+ * this.pausePolling();
69
+ * }
70
+ * },
71
+ * clientControllerSelectors.selectIsUiOpen,
72
+ * );
73
+ * }
74
+ * }
75
+ * ```
76
+ */
77
+ export class ClientController extends BaseController {
78
+ /**
79
+ * Constructs a new {@link ClientController}.
80
+ *
81
+ * @param options - The constructor options.
82
+ * @param options.messenger - The messenger suited for this controller.
83
+ * @param options.state - The initial state to set on this controller.
84
+ */
85
+ constructor({ messenger, state = {} }) {
86
+ super({
87
+ messenger,
88
+ metadata: controllerMetadata,
89
+ name: controllerName,
90
+ state: {
91
+ ...getDefaultClientControllerState(),
92
+ ...state,
93
+ },
94
+ });
95
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
96
+ }
97
+ /**
98
+ * Updates state with whether the MetaMask UI is open.
99
+ *
100
+ * This method should be called when the user has opened the first window or
101
+ * screen containing the MetaMask UI, or closed the last window or screen
102
+ * containing the MetaMask UI.
103
+ *
104
+ * @param open - Whether the MetaMask UI is open.
105
+ */
106
+ setUiOpen(open) {
107
+ this.update((state) => {
108
+ state.isUiOpen = open;
109
+ });
110
+ }
111
+ }
112
+ //# sourceMappingURL=ClientController.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientController.mjs","sourceRoot":"","sources":["../src/ClientController.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAK3D,kBAAkB;AAElB;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAgBjD;;;;GAIG;AACH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;QACL,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,kBAAkB,GAAG;IACzB,QAAQ,EAAE;QACR,sBAAsB,EAAE,IAAI;QAC5B,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK;KAChB;CAC6C,CAAC;AAEjD,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG,CAAC,WAAW,CAAU,CAAC;AAiEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,OAAO,gBAAiB,SAAQ,cAIrC;IACC;;;;;;OAMG;IACH,YAAY,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,EAA2B;QAC5D,KAAK,CAAC;YACJ,SAAS;YACT,QAAQ,EAAE,kBAAkB;YAC5B,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE;gBACL,GAAG,+BAA+B,EAAE;gBACpC,GAAG,KAAK;aACT;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAa;QACrB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import type {\n StateMetadata,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { ClientControllerMethodActions } from './ClientController-method-action-types';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ClientController}.\n */\nexport const controllerName = 'ClientController';\n\n// === STATE ===\n\n/**\n * Describes the shape of the state object for {@link ClientController}.\n */\nexport type ClientControllerState = {\n /**\n * Whether the user has opened at least one window or screen\n * containing the MetaMask UI. These windows or screens may or\n * may not be in an inactive state.\n */\n isUiOpen: boolean;\n};\n\n/**\n * Constructs the default {@link ClientController} state.\n *\n * @returns The default {@link ClientController} state.\n */\nexport function getDefaultClientControllerState(): ClientControllerState {\n return {\n isUiOpen: false,\n };\n}\n\n/**\n * The metadata for each property in {@link ClientControllerState}.\n */\nconst controllerMetadata = {\n isUiOpen: {\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n persist: false,\n usedInUi: false,\n },\n} satisfies StateMetadata<ClientControllerState>;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['setUiOpen'] as const;\n\n/**\n * Retrieves the state of the {@link ClientController}.\n */\nexport type ClientControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ClientControllerState\n>;\n\n/**\n * Actions that {@link ClientController} exposes.\n */\nexport type ClientControllerActions =\n | ClientControllerGetStateAction\n | ClientControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link ClientController} calls.\n */\ntype AllowedActions = never;\n\n/**\n * Published when the state of {@link ClientController} changes.\n */\nexport type ClientControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n ClientControllerState\n>;\n\n/**\n * Events that {@link ClientController} exposes.\n */\nexport type ClientControllerEvents = ClientControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ClientController} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger for {@link ClientController}.\n */\nexport type ClientControllerMessenger = Messenger<\n typeof controllerName,\n ClientControllerActions | AllowedActions,\n ClientControllerEvents | AllowedEvents\n>;\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * The options for constructing a {@link ClientController}.\n */\nexport type ClientControllerOptions = {\n /**\n * The messenger suited for this controller.\n */\n messenger: ClientControllerMessenger;\n /**\n * The initial state to set on this controller.\n */\n state?: Partial<ClientControllerState>;\n};\n\n/**\n * `ClientController` manages the application lifecycle state.\n *\n * This controller tracks whether the MetaMask UI is open and publishes state\n * change events that other controllers can subscribe to for adjusting their behavior.\n *\n * **Use cases:**\n * - Polling controllers can pause when the UI closes, resume when it opens\n * - WebSocket connections can disconnect when closed, reconnect when opened\n * - Real-time subscriptions can pause when not visible\n *\n * **Platform Integration:**\n * Platform code should call `ClientController:setUiOpen` via messenger.\n *\n * @example\n * ```typescript\n * // In MetamaskController or platform code\n * onUiOpened() {\n * // ...\n * this.controllerMessenger.call('ClientController:setUiOpen', true);\n * }\n *\n * onUiClosed() {\n * // ...\n * this.controllerMessenger.call('ClientController:setUiOpen', false);\n * }\n *\n * // Consumer controller subscribing to state changes\n * class MyController extends BaseController {\n * constructor({ messenger }) {\n * super({ messenger, ... });\n *\n * messenger.subscribe(\n * 'ClientController:stateChange',\n * (isClientOpen) => {\n * if (isClientOpen) {\n * this.resumePolling();\n * } else {\n * this.pausePolling();\n * }\n * },\n * clientControllerSelectors.selectIsUiOpen,\n * );\n * }\n * }\n * ```\n */\nexport class ClientController extends BaseController<\n typeof controllerName,\n ClientControllerState,\n ClientControllerMessenger\n> {\n /**\n * Constructs a new {@link ClientController}.\n *\n * @param options - The constructor options.\n * @param options.messenger - The messenger suited for this controller.\n * @param options.state - The initial state to set on this controller.\n */\n constructor({ messenger, state = {} }: ClientControllerOptions) {\n super({\n messenger,\n metadata: controllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultClientControllerState(),\n ...state,\n },\n });\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Updates state with whether the MetaMask UI is open.\n *\n * This method should be called when the user has opened the first window or\n * screen containing the MetaMask UI, or closed the last window or screen\n * containing the MetaMask UI.\n *\n * @param open - Whether the MetaMask UI is open.\n */\n setUiOpen(open: boolean): void {\n this.update((state) => {\n state.isUiOpen = open;\n });\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clientControllerSelectors = exports.getDefaultClientControllerState = exports.ClientController = void 0;
4
+ var ClientController_1 = require("./ClientController.cjs");
5
+ Object.defineProperty(exports, "ClientController", { enumerable: true, get: function () { return ClientController_1.ClientController; } });
6
+ Object.defineProperty(exports, "getDefaultClientControllerState", { enumerable: true, get: function () { return ClientController_1.getDefaultClientControllerState; } });
7
+ var selectors_1 = require("./selectors.cjs");
8
+ Object.defineProperty(exports, "clientControllerSelectors", { enumerable: true, get: function () { return selectors_1.clientControllerSelectors; } });
9
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2DAG4B;AAF1B,oHAAA,gBAAgB,OAAA;AAChB,mIAAA,+BAA+B,OAAA;AAEjC,6CAAwD;AAA/C,sHAAA,yBAAyB,OAAA","sourcesContent":["export {\n ClientController,\n getDefaultClientControllerState,\n} from './ClientController';\nexport { clientControllerSelectors } from './selectors';\n\nexport type {\n ClientControllerState,\n ClientControllerOptions,\n ClientControllerGetStateAction,\n ClientControllerActions,\n ClientControllerStateChangeEvent,\n ClientControllerEvents,\n ClientControllerMessenger,\n} from './ClientController';\nexport type { ClientControllerSetUiOpenAction } from './ClientController-method-action-types';\n"]}
@@ -0,0 +1,5 @@
1
+ export { ClientController, getDefaultClientControllerState, } from "./ClientController.cjs";
2
+ export { clientControllerSelectors } from "./selectors.cjs";
3
+ export type { ClientControllerState, ClientControllerOptions, ClientControllerGetStateAction, ClientControllerActions, ClientControllerStateChangeEvent, ClientControllerEvents, ClientControllerMessenger, } from "./ClientController.cjs";
4
+ export type { ClientControllerSetUiOpenAction } from "./ClientController-method-action-types.cjs";
5
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,+BAA+B,GAChC,+BAA2B;AAC5B,OAAO,EAAE,yBAAyB,EAAE,wBAAoB;AAExD,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,uBAAuB,EACvB,gCAAgC,EAChC,sBAAsB,EACtB,yBAAyB,GAC1B,+BAA2B;AAC5B,YAAY,EAAE,+BAA+B,EAAE,mDAA+C"}
@@ -0,0 +1,5 @@
1
+ export { ClientController, getDefaultClientControllerState, } from "./ClientController.mjs";
2
+ export { clientControllerSelectors } from "./selectors.mjs";
3
+ export type { ClientControllerState, ClientControllerOptions, ClientControllerGetStateAction, ClientControllerActions, ClientControllerStateChangeEvent, ClientControllerEvents, ClientControllerMessenger, } from "./ClientController.mjs";
4
+ export type { ClientControllerSetUiOpenAction } from "./ClientController-method-action-types.mjs";
5
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,+BAA+B,GAChC,+BAA2B;AAC5B,OAAO,EAAE,yBAAyB,EAAE,wBAAoB;AAExD,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,uBAAuB,EACvB,gCAAgC,EAChC,sBAAsB,EACtB,yBAAyB,GAC1B,+BAA2B;AAC5B,YAAY,EAAE,+BAA+B,EAAE,mDAA+C"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ export { ClientController, getDefaultClientControllerState } from "./ClientController.mjs";
2
+ export { clientControllerSelectors } from "./selectors.mjs";
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,+BAA+B,EAChC,+BAA2B;AAC5B,OAAO,EAAE,yBAAyB,EAAE,wBAAoB","sourcesContent":["export {\n ClientController,\n getDefaultClientControllerState,\n} from './ClientController';\nexport { clientControllerSelectors } from './selectors';\n\nexport type {\n ClientControllerState,\n ClientControllerOptions,\n ClientControllerGetStateAction,\n ClientControllerActions,\n ClientControllerStateChangeEvent,\n ClientControllerEvents,\n ClientControllerMessenger,\n} from './ClientController';\nexport type { ClientControllerSetUiOpenAction } from './ClientController-method-action-types';\n"]}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clientControllerSelectors = void 0;
4
+ /**
5
+ * Selects whether the UI is currently open.
6
+ *
7
+ * @param state - The ClientController state.
8
+ * @returns True if the UI is open.
9
+ */
10
+ const selectIsUiOpen = (state) => state.isUiOpen;
11
+ /**
12
+ * Selectors for the ClientController state.
13
+ * These can be used with Redux or directly with controller state.
14
+ */
15
+ exports.clientControllerSelectors = {
16
+ selectIsUiOpen,
17
+ };
18
+ //# sourceMappingURL=selectors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.cjs","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,KAA4B,EAAW,EAAE,CAC/D,KAAK,CAAC,QAAQ,CAAC;AAEjB;;;GAGG;AACU,QAAA,yBAAyB,GAAG;IACvC,cAAc;CACf,CAAC","sourcesContent":["import type { ClientControllerState } from './ClientController';\n\n/**\n * Selects whether the UI is currently open.\n *\n * @param state - The ClientController state.\n * @returns True if the UI is open.\n */\nconst selectIsUiOpen = (state: ClientControllerState): boolean =>\n state.isUiOpen;\n\n/**\n * Selectors for the ClientController state.\n * These can be used with Redux or directly with controller state.\n */\nexport const clientControllerSelectors = {\n selectIsUiOpen,\n};\n"]}
@@ -0,0 +1,9 @@
1
+ import type { ClientControllerState } from "./ClientController.cjs";
2
+ /**
3
+ * Selectors for the ClientController state.
4
+ * These can be used with Redux or directly with controller state.
5
+ */
6
+ export declare const clientControllerSelectors: {
7
+ selectIsUiOpen: (state: ClientControllerState) => boolean;
8
+ };
9
+ //# sourceMappingURL=selectors.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.d.cts","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,+BAA2B;AAWhE;;;GAGG;AACH,eAAO,MAAM,yBAAyB;4BAPP,qBAAqB,KAAG,OAAO;CAS7D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ClientControllerState } from "./ClientController.mjs";
2
+ /**
3
+ * Selectors for the ClientController state.
4
+ * These can be used with Redux or directly with controller state.
5
+ */
6
+ export declare const clientControllerSelectors: {
7
+ selectIsUiOpen: (state: ClientControllerState) => boolean;
8
+ };
9
+ //# sourceMappingURL=selectors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.d.mts","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,+BAA2B;AAWhE;;;GAGG;AACH,eAAO,MAAM,yBAAyB;4BAPP,qBAAqB,KAAG,OAAO;CAS7D,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Selects whether the UI is currently open.
3
+ *
4
+ * @param state - The ClientController state.
5
+ * @returns True if the UI is open.
6
+ */
7
+ const selectIsUiOpen = (state) => state.isUiOpen;
8
+ /**
9
+ * Selectors for the ClientController state.
10
+ * These can be used with Redux or directly with controller state.
11
+ */
12
+ export const clientControllerSelectors = {
13
+ selectIsUiOpen,
14
+ };
15
+ //# sourceMappingURL=selectors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.mjs","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,KAA4B,EAAW,EAAE,CAC/D,KAAK,CAAC,QAAQ,CAAC;AAEjB;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,cAAc;CACf,CAAC","sourcesContent":["import type { ClientControllerState } from './ClientController';\n\n/**\n * Selects whether the UI is currently open.\n *\n * @param state - The ClientController state.\n * @returns True if the UI is open.\n */\nconst selectIsUiOpen = (state: ClientControllerState): boolean =>\n state.isUiOpen;\n\n/**\n * Selectors for the ClientController state.\n * These can be used with Redux or directly with controller state.\n */\nexport const clientControllerSelectors = {\n selectIsUiOpen,\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@metamask-previews/client-controller",
3
+ "version": "0.0.0-preview-a196307b6",
4
+ "description": "Client-level state for MetaMask (e.g. whether a UI window is open)",
5
+ "keywords": [
6
+ "MetaMask",
7
+ "Ethereum"
8
+ ],
9
+ "homepage": "https://github.com/MetaMask/core/tree/main/packages/client-controller#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/MetaMask/core/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/MetaMask/core.git"
16
+ },
17
+ "license": "MIT",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.mts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "main": "./dist/index.cjs",
33
+ "types": "./dist/index.d.cts",
34
+ "files": [
35
+ "dist/"
36
+ ],
37
+ "scripts": {
38
+ "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
39
+ "build:all": "ts-bridge --project tsconfig.build.json --verbose --clean",
40
+ "build:docs": "typedoc",
41
+ "changelog:update": "../../scripts/update-changelog.sh @metamask/client-controller",
42
+ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/client-controller",
43
+ "publish:preview": "yarn npm publish --tag preview",
44
+ "since-latest-release": "../../scripts/since-latest-release.sh",
45
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
46
+ "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
47
+ "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
48
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
49
+ },
50
+ "dependencies": {
51
+ "@metamask/base-controller": "^9.0.0",
52
+ "@metamask/messenger": "^0.3.0"
53
+ },
54
+ "devDependencies": {
55
+ "@metamask/auto-changelog": "^3.4.4",
56
+ "@ts-bridge/cli": "^0.6.4",
57
+ "@types/jest": "^29.5.14",
58
+ "deepmerge": "^4.2.2",
59
+ "jest": "^29.7.0",
60
+ "jest-environment-jsdom": "^29.7.0",
61
+ "ts-jest": "^29.2.5",
62
+ "typedoc": "^0.25.13",
63
+ "typedoc-plugin-missing-exports": "^2.0.0",
64
+ "typescript": "~5.3.3"
65
+ },
66
+ "engines": {
67
+ "node": "^18.18 || >=20"
68
+ },
69
+ "publishConfig": {
70
+ "access": "public",
71
+ "registry": "https://registry.npmjs.org/"
72
+ }
73
+ }