@metamask-previews/snap-account-service 0.2.0-preview-8e8c4f6f5 → 0.2.0-preview-e43dfcb
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.
- package/CHANGELOG.md +0 -20
- package/dist/SnapAccountService-method-action-types.cjs.map +1 -1
- package/dist/SnapAccountService-method-action-types.d.cts +8 -13
- package/dist/SnapAccountService-method-action-types.d.cts.map +1 -1
- package/dist/SnapAccountService-method-action-types.d.mts +8 -13
- package/dist/SnapAccountService-method-action-types.d.mts.map +1 -1
- package/dist/SnapAccountService-method-action-types.mjs.map +1 -1
- package/dist/SnapAccountService.cjs +38 -204
- package/dist/SnapAccountService.cjs.map +1 -1
- package/dist/SnapAccountService.d.cts +10 -15
- package/dist/SnapAccountService.d.cts.map +1 -1
- package/dist/SnapAccountService.d.mts +10 -15
- package/dist/SnapAccountService.d.mts.map +1 -1
- package/dist/SnapAccountService.mjs +39 -205
- package/dist/SnapAccountService.mjs.map +1 -1
- package/dist/SnapPlatformWatcher.cjs +66 -3
- package/dist/SnapPlatformWatcher.cjs.map +1 -1
- package/dist/SnapPlatformWatcher.d.cts +8 -0
- package/dist/SnapPlatformWatcher.d.cts.map +1 -1
- package/dist/SnapPlatformWatcher.d.mts +8 -0
- package/dist/SnapPlatformWatcher.d.mts.map +1 -1
- package/dist/SnapPlatformWatcher.mjs +66 -3
- package/dist/SnapPlatformWatcher.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,26 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
### Added
|
|
11
|
-
|
|
12
|
-
- Add `migrate` ([#8732](https://github.com/MetaMask/core/pull/8732))
|
|
13
|
-
- The migration is guaranteed to be run when using `ensureReady`.
|
|
14
|
-
- If the migration is not successful, it will get retried everytime we need to interact with any Snap keyring (v2) instances.
|
|
15
|
-
- It is conccurent-free and can safely be called by multiple execution flows.
|
|
16
|
-
- Once the migration has ran, the legacy Snap keyring will be emptied, thus, consumers are expected to use the new per-Snap keyring (v2) instances instead.
|
|
17
|
-
- Selected-account forwarding now targets v2 Snap keyrings.
|
|
18
|
-
- The service messenger now requires the `KeyringController:withKeyringV2Unsafe`.
|
|
19
|
-
|
|
20
|
-
### Changed
|
|
21
|
-
|
|
22
|
-
- `SnapAccountService.ensureReady` now automatically creates the Snap keyring (v2) for a given Snap ID if it was not available ([#8732](https://github.com/MetaMask/core/pull/8732))
|
|
23
|
-
- Bump `@metamask/eth-snap-keyring` from `^22.0.1` to `^22.1.0` ([#8732](https://github.com/MetaMask/core/pull/8732))
|
|
24
|
-
|
|
25
|
-
### Removed
|
|
26
|
-
|
|
27
|
-
- **BREAKING:** Removed `getLegacySnapKeyring` ([#8732](https://github.com/MetaMask/core/pull/8732))
|
|
28
|
-
- The legacy Snap keyring should not be used anymore after the migration has completed.
|
|
29
|
-
|
|
30
10
|
## [0.2.0]
|
|
31
11
|
|
|
32
12
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapAccountService-method-action-types.cjs","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { SnapAccountService } from './SnapAccountService';\n\n/**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\nexport type SnapAccountServiceGetSnapsAction = {\n type: `SnapAccountService:getSnaps`;\n handler: SnapAccountService['getSnaps'];\n};\n\n/**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2.
|
|
1
|
+
{"version":3,"file":"SnapAccountService-method-action-types.cjs","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { SnapAccountService } from './SnapAccountService';\n\n/**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\nexport type SnapAccountServiceGetSnapsAction = {\n type: `SnapAccountService:getSnaps`;\n handler: SnapAccountService['getSnaps'];\n};\n\n/**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2. Waits for the Snap platform to be fully started.\n *\n * Safe to call concurrently — each step is idempotent or mutex-protected.\n *\n * @param snapId - ID of the Snap to ensure readiness for.\n * @throws If `snapId` is not a tracked account-management Snap.\n */\nexport type SnapAccountServiceEnsureReadyAction = {\n type: `SnapAccountService:ensureReady`;\n handler: SnapAccountService['ensureReady'];\n};\n\n/**\n * Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring\n * associated with {@link KeyringTypes.snap}.\n *\n * @returns The existing or newly-created Snap keyring instance.\n */\nexport type SnapAccountServiceGetLegacySnapKeyringAction = {\n type: `SnapAccountService:getLegacySnapKeyring`;\n handler: SnapAccountService['getLegacySnapKeyring'];\n};\n\n/**\n * Handle a message from a Snap.\n *\n * @param snapId - ID of the Snap.\n * @param message - Message sent by the Snap.\n * @returns The execution result.\n */\nexport type SnapAccountServiceHandleKeyringSnapMessageAction = {\n type: `SnapAccountService:handleKeyringSnapMessage`;\n handler: SnapAccountService['handleKeyringSnapMessage'];\n};\n\n/**\n * Union of all SnapAccountService action types.\n */\nexport type SnapAccountServiceMethodActions =\n | SnapAccountServiceGetSnapsAction\n | SnapAccountServiceEnsureReadyAction\n | SnapAccountServiceGetLegacySnapKeyringAction\n | SnapAccountServiceHandleKeyringSnapMessageAction;\n"]}
|
|
@@ -17,11 +17,7 @@ export type SnapAccountServiceGetSnapsAction = {
|
|
|
17
17
|
/**
|
|
18
18
|
* Ensures everything is ready to use Snap accounts for the given Snap.
|
|
19
19
|
* 1. Validates that `snapId` is a tracked account-management Snap.
|
|
20
|
-
* 2.
|
|
21
|
-
* already done).
|
|
22
|
-
* 3. Atomically creates the v2 keyring for this Snap if it doesn't exist
|
|
23
|
-
* yet.
|
|
24
|
-
* 4. Waits for the Snap platform to be fully started.
|
|
20
|
+
* 2. Waits for the Snap platform to be fully started.
|
|
25
21
|
*
|
|
26
22
|
* Safe to call concurrently — each step is idempotent or mutex-protected.
|
|
27
23
|
*
|
|
@@ -33,15 +29,14 @@ export type SnapAccountServiceEnsureReadyAction = {
|
|
|
33
29
|
handler: SnapAccountService['ensureReady'];
|
|
34
30
|
};
|
|
35
31
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* await the same promise.
|
|
32
|
+
* Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring
|
|
33
|
+
* associated with {@link KeyringTypes.snap}.
|
|
39
34
|
*
|
|
40
|
-
* @returns
|
|
35
|
+
* @returns The existing or newly-created Snap keyring instance.
|
|
41
36
|
*/
|
|
42
|
-
export type
|
|
43
|
-
type: `SnapAccountService:
|
|
44
|
-
handler: SnapAccountService['
|
|
37
|
+
export type SnapAccountServiceGetLegacySnapKeyringAction = {
|
|
38
|
+
type: `SnapAccountService:getLegacySnapKeyring`;
|
|
39
|
+
handler: SnapAccountService['getLegacySnapKeyring'];
|
|
45
40
|
};
|
|
46
41
|
/**
|
|
47
42
|
* Handle a message from a Snap.
|
|
@@ -57,5 +52,5 @@ export type SnapAccountServiceHandleKeyringSnapMessageAction = {
|
|
|
57
52
|
/**
|
|
58
53
|
* Union of all SnapAccountService action types.
|
|
59
54
|
*/
|
|
60
|
-
export type SnapAccountServiceMethodActions = SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction |
|
|
55
|
+
export type SnapAccountServiceMethodActions = SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction | SnapAccountServiceGetLegacySnapKeyringAction | SnapAccountServiceHandleKeyringSnapMessageAction;
|
|
61
56
|
//# sourceMappingURL=SnapAccountService-method-action-types.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapAccountService-method-action-types.d.cts","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iCAA6B;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,6BAA6B,CAAC;IACpC,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CACzC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"SnapAccountService-method-action-types.d.cts","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iCAA6B;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,6BAA6B,CAAC;IACpC,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CACzC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;CAC5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,4CAA4C,GAAG;IACzD,IAAI,EAAE,yCAAyC,CAAC;IAChD,OAAO,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,gDAAgD,GAAG;IAC7D,IAAI,EAAE,6CAA6C,CAAC;IACpD,OAAO,EAAE,kBAAkB,CAAC,0BAA0B,CAAC,CAAC;CACzD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,gCAAgC,GAChC,mCAAmC,GACnC,4CAA4C,GAC5C,gDAAgD,CAAC"}
|
|
@@ -17,11 +17,7 @@ export type SnapAccountServiceGetSnapsAction = {
|
|
|
17
17
|
/**
|
|
18
18
|
* Ensures everything is ready to use Snap accounts for the given Snap.
|
|
19
19
|
* 1. Validates that `snapId` is a tracked account-management Snap.
|
|
20
|
-
* 2.
|
|
21
|
-
* already done).
|
|
22
|
-
* 3. Atomically creates the v2 keyring for this Snap if it doesn't exist
|
|
23
|
-
* yet.
|
|
24
|
-
* 4. Waits for the Snap platform to be fully started.
|
|
20
|
+
* 2. Waits for the Snap platform to be fully started.
|
|
25
21
|
*
|
|
26
22
|
* Safe to call concurrently — each step is idempotent or mutex-protected.
|
|
27
23
|
*
|
|
@@ -33,15 +29,14 @@ export type SnapAccountServiceEnsureReadyAction = {
|
|
|
33
29
|
handler: SnapAccountService['ensureReady'];
|
|
34
30
|
};
|
|
35
31
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* await the same promise.
|
|
32
|
+
* Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring
|
|
33
|
+
* associated with {@link KeyringTypes.snap}.
|
|
39
34
|
*
|
|
40
|
-
* @returns
|
|
35
|
+
* @returns The existing or newly-created Snap keyring instance.
|
|
41
36
|
*/
|
|
42
|
-
export type
|
|
43
|
-
type: `SnapAccountService:
|
|
44
|
-
handler: SnapAccountService['
|
|
37
|
+
export type SnapAccountServiceGetLegacySnapKeyringAction = {
|
|
38
|
+
type: `SnapAccountService:getLegacySnapKeyring`;
|
|
39
|
+
handler: SnapAccountService['getLegacySnapKeyring'];
|
|
45
40
|
};
|
|
46
41
|
/**
|
|
47
42
|
* Handle a message from a Snap.
|
|
@@ -57,5 +52,5 @@ export type SnapAccountServiceHandleKeyringSnapMessageAction = {
|
|
|
57
52
|
/**
|
|
58
53
|
* Union of all SnapAccountService action types.
|
|
59
54
|
*/
|
|
60
|
-
export type SnapAccountServiceMethodActions = SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction |
|
|
55
|
+
export type SnapAccountServiceMethodActions = SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction | SnapAccountServiceGetLegacySnapKeyringAction | SnapAccountServiceHandleKeyringSnapMessageAction;
|
|
61
56
|
//# sourceMappingURL=SnapAccountService-method-action-types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapAccountService-method-action-types.d.mts","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iCAA6B;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,6BAA6B,CAAC;IACpC,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CACzC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"SnapAccountService-method-action-types.d.mts","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iCAA6B;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,6BAA6B,CAAC;IACpC,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CACzC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;CAC5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,4CAA4C,GAAG;IACzD,IAAI,EAAE,yCAAyC,CAAC;IAChD,OAAO,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,gDAAgD,GAAG;IAC7D,IAAI,EAAE,6CAA6C,CAAC;IACpD,OAAO,EAAE,kBAAkB,CAAC,0BAA0B,CAAC,CAAC;CACzD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,gCAAgC,GAChC,mCAAmC,GACnC,4CAA4C,GAC5C,gDAAgD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapAccountService-method-action-types.mjs","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { SnapAccountService } from './SnapAccountService';\n\n/**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\nexport type SnapAccountServiceGetSnapsAction = {\n type: `SnapAccountService:getSnaps`;\n handler: SnapAccountService['getSnaps'];\n};\n\n/**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2.
|
|
1
|
+
{"version":3,"file":"SnapAccountService-method-action-types.mjs","sourceRoot":"","sources":["../src/SnapAccountService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { SnapAccountService } from './SnapAccountService';\n\n/**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\nexport type SnapAccountServiceGetSnapsAction = {\n type: `SnapAccountService:getSnaps`;\n handler: SnapAccountService['getSnaps'];\n};\n\n/**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2. Waits for the Snap platform to be fully started.\n *\n * Safe to call concurrently — each step is idempotent or mutex-protected.\n *\n * @param snapId - ID of the Snap to ensure readiness for.\n * @throws If `snapId` is not a tracked account-management Snap.\n */\nexport type SnapAccountServiceEnsureReadyAction = {\n type: `SnapAccountService:ensureReady`;\n handler: SnapAccountService['ensureReady'];\n};\n\n/**\n * Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring\n * associated with {@link KeyringTypes.snap}.\n *\n * @returns The existing or newly-created Snap keyring instance.\n */\nexport type SnapAccountServiceGetLegacySnapKeyringAction = {\n type: `SnapAccountService:getLegacySnapKeyring`;\n handler: SnapAccountService['getLegacySnapKeyring'];\n};\n\n/**\n * Handle a message from a Snap.\n *\n * @param snapId - ID of the Snap.\n * @param message - Message sent by the Snap.\n * @returns The execution result.\n */\nexport type SnapAccountServiceHandleKeyringSnapMessageAction = {\n type: `SnapAccountService:handleKeyringSnapMessage`;\n handler: SnapAccountService['handleKeyringSnapMessage'];\n};\n\n/**\n * Union of all SnapAccountService action types.\n */\nexport type SnapAccountServiceMethodActions =\n | SnapAccountServiceGetSnapsAction\n | SnapAccountServiceEnsureReadyAction\n | SnapAccountServiceGetLegacySnapKeyringAction\n | SnapAccountServiceHandleKeyringSnapMessageAction;\n"]}
|
|
@@ -10,14 +10,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _SnapAccountService_instances, _SnapAccountService_messenger, _SnapAccountService_watcher, _SnapAccountService_tracker,
|
|
13
|
+
var _SnapAccountService_instances, _SnapAccountService_messenger, _SnapAccountService_watcher, _SnapAccountService_tracker, _SnapAccountService_handleSelectedAccountGroupChange, _SnapAccountService_handleUnlock, _SnapAccountService_handleAccountGroupCreatedOrUpdated, _SnapAccountService_handleAccountGroupRemoved, _SnapAccountService_forwardSelectedAccounts, _SnapAccountService_getAccountGroup, _SnapAccountService_getSelectedAccountGroupId;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.SnapAccountService = exports.serviceName = void 0;
|
|
16
|
-
const v2_1 = require("@metamask/eth-snap-keyring/v2");
|
|
17
|
-
const keyring_api_1 = require("@metamask/keyring-api");
|
|
18
|
-
const v2_2 = require("@metamask/keyring-api/v2");
|
|
19
16
|
const keyring_controller_1 = require("@metamask/keyring-controller");
|
|
20
|
-
const keyring_snap_sdk_1 = require("@metamask/keyring-snap-sdk");
|
|
21
17
|
const logger_1 = require("./logger.cjs");
|
|
22
18
|
const SnapPlatformWatcher_1 = require("./SnapPlatformWatcher.cjs");
|
|
23
19
|
const SnapTracker_1 = require("./SnapTracker.cjs");
|
|
@@ -33,8 +29,8 @@ exports.serviceName = 'SnapAccountService';
|
|
|
33
29
|
const MESSENGER_EXPOSED_METHODS = [
|
|
34
30
|
'ensureReady',
|
|
35
31
|
'getSnaps',
|
|
32
|
+
'getLegacySnapKeyring',
|
|
36
33
|
'handleKeyringSnapMessage',
|
|
37
|
-
'migrate',
|
|
38
34
|
];
|
|
39
35
|
/**
|
|
40
36
|
* Checks if a given keyring is a Snap keyring (v2).
|
|
@@ -62,8 +58,6 @@ class SnapAccountService {
|
|
|
62
58
|
_SnapAccountService_messenger.set(this, void 0);
|
|
63
59
|
_SnapAccountService_watcher.set(this, void 0);
|
|
64
60
|
_SnapAccountService_tracker.set(this, void 0);
|
|
65
|
-
_SnapAccountService_migrated.set(this, false);
|
|
66
|
-
_SnapAccountService_migratePromise.set(this, null);
|
|
67
61
|
this.name = exports.serviceName;
|
|
68
62
|
__classPrivateFieldSet(this, _SnapAccountService_messenger, messenger, "f");
|
|
69
63
|
__classPrivateFieldSet(this, _SnapAccountService_watcher, new SnapPlatformWatcher_1.SnapPlatformWatcher(messenger, config?.snapPlatformWatcher), "f");
|
|
@@ -98,11 +92,7 @@ class SnapAccountService {
|
|
|
98
92
|
/**
|
|
99
93
|
* Ensures everything is ready to use Snap accounts for the given Snap.
|
|
100
94
|
* 1. Validates that `snapId` is a tracked account-management Snap.
|
|
101
|
-
* 2.
|
|
102
|
-
* already done).
|
|
103
|
-
* 3. Atomically creates the v2 keyring for this Snap if it doesn't exist
|
|
104
|
-
* yet.
|
|
105
|
-
* 4. Waits for the Snap platform to be fully started.
|
|
95
|
+
* 2. Waits for the Snap platform to be fully started.
|
|
106
96
|
*
|
|
107
97
|
* Safe to call concurrently — each step is idempotent or mutex-protected.
|
|
108
98
|
*
|
|
@@ -113,40 +103,38 @@ class SnapAccountService {
|
|
|
113
103
|
if (!__classPrivateFieldGet(this, _SnapAccountService_tracker, "f").canUse(snapId)) {
|
|
114
104
|
throw new Error(`Unknown snap: "${snapId}"`);
|
|
115
105
|
}
|
|
116
|
-
// Migrate from the global v1 Snap keyring to the per-Snap v2 keyring
|
|
117
|
-
// before doing anything else.
|
|
118
|
-
await this.migrate();
|
|
119
|
-
// We still try to create the keyring for the Snap here, since we might
|
|
120
|
-
// want to use a new Snap that never had accounts before.
|
|
121
|
-
await __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_ensureKeyringIsReady).call(this, snapId);
|
|
122
106
|
// Before doing anything with our Snap, we need to make sure the platform
|
|
123
107
|
// is ready to process requests.
|
|
124
108
|
await __classPrivateFieldGet(this, _SnapAccountService_watcher, "f").ensureCanUseSnapPlatform();
|
|
125
109
|
}
|
|
126
110
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* await the same promise.
|
|
111
|
+
* Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring
|
|
112
|
+
* associated with {@link KeyringTypes.snap}.
|
|
130
113
|
*
|
|
131
|
-
* @returns
|
|
114
|
+
* @returns The existing or newly-created Snap keyring instance.
|
|
132
115
|
*/
|
|
133
|
-
async
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
116
|
+
async getLegacySnapKeyring() {
|
|
117
|
+
// `KeyringController:withController` forbids returning a direct keyring
|
|
118
|
+
// reference (it checks the result via `Object.is`), so we smuggle the
|
|
119
|
+
// instance out wrapped in an object and unwrap it after the call.
|
|
120
|
+
// NOTE: This violates the abstraction of `KeyringController:withController`, but this
|
|
121
|
+
// is how we currently interact with the legacy Snap keyring. Once we migrate it to
|
|
122
|
+
// the Snap keyring v2, we won't be using the same pattern.
|
|
123
|
+
const result = await __classPrivateFieldGet(this, _SnapAccountService_messenger, "f").call('KeyringController:withController', async (controller) => {
|
|
124
|
+
let snapKeyring;
|
|
125
|
+
const found = controller.keyrings.find(({ keyring }) => isLegacySnapKeyring(keyring));
|
|
126
|
+
if (found) {
|
|
127
|
+
snapKeyring = found.keyring;
|
|
144
128
|
}
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
if (!snapKeyring) {
|
|
130
|
+
const { keyring: newSnapKeyring, metadata: { id }, } = await controller.addNewKeyring(keyring_controller_1.KeyringTypes.snap);
|
|
131
|
+
snapKeyring = newSnapKeyring;
|
|
132
|
+
(0, logger_1.projectLogger)(`Legacy Snap keyring created. ("${id}")`);
|
|
147
133
|
}
|
|
148
|
-
|
|
149
|
-
|
|
134
|
+
// The legacy Snap keyring is not compatible with `EthKeyring`, so we need to cast here.
|
|
135
|
+
return { snapKeyring };
|
|
136
|
+
});
|
|
137
|
+
return result.snapKeyring;
|
|
150
138
|
}
|
|
151
139
|
/**
|
|
152
140
|
* Handle a message from a Snap.
|
|
@@ -156,46 +144,12 @@ class SnapAccountService {
|
|
|
156
144
|
* @returns The execution result.
|
|
157
145
|
*/
|
|
158
146
|
async handleKeyringSnapMessage(snapId, message) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const groupId = __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_getSelectedAccountGroupId).call(this);
|
|
162
|
-
const accounts = __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_getAccountGroup).call(this, groupId)?.accounts ?? [];
|
|
163
|
-
return await __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_withKeyringV2Unsafe).call(this, snapId, async (keyring) => accounts.filter((id) => keyring.hasAccount(id)));
|
|
164
|
-
}
|
|
165
|
-
const event = message.method; // We assume the Snap platform always sends a valid `KeyringEvent` here.
|
|
166
|
-
(0, logger_1.projectLogger)(`Forwarding message "${event}" from Snap "${snapId}" to its keyring...`);
|
|
167
|
-
// We can create a new keyring if the message is an AccountCreated event.
|
|
168
|
-
const isAccountCreatedMessage = event === keyring_api_1.KeyringEvent.AccountCreated;
|
|
169
|
-
// Create the Snap keyring if it doesn't exist yet (in an atomic way). We cannot assume
|
|
170
|
-
// the keyring exists (e.g for the MMI Snap).
|
|
171
|
-
// NOTE: We only auto-create it for v1 account creation flows.
|
|
172
|
-
if (isAccountCreatedMessage) {
|
|
173
|
-
await __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_ensureKeyringIsReady).call(this, snapId);
|
|
174
|
-
}
|
|
175
|
-
// This part of the flow relies on v1 flows, but v2 keyrings are compatible with those messages
|
|
176
|
-
// too.
|
|
177
|
-
try {
|
|
178
|
-
// NOTE: We use "unsafe" here since none of the messages should trigger mutations to the keyring state.
|
|
179
|
-
// The exception might be `:accountCreated`, but even in that case, the mutation is handled differently
|
|
180
|
-
// in the client by call `:persistAllKeyrings` explicitly.
|
|
181
|
-
// Using `:withKeyringV2` would cause a deadlock when we're initiating operations like `removeAccount` from
|
|
182
|
-
// the keyring itself:
|
|
183
|
-
// 1: withKeyring(..., ({ keyring }) => { keyring.removeAccount(...) })
|
|
184
|
-
// 2. removeAccount(...) -> handleKeyringSnapMessage(..., { method: 'accountRemoved', ... })
|
|
185
|
-
// 3. handleKeyringSnapMessage tries to acquire the same lock again via withKeyringV2 -> deadlock.
|
|
186
|
-
return await __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_withKeyringV2Unsafe).call(this, snapId, async (keyring) => keyring.handleKeyringSnapMessage(message));
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
if ((0, keyring_controller_1.isKeyringNotFoundError)(error)) {
|
|
190
|
-
(0, logger_1.projectLogger)(`No Snap keyring found for Snap "${snapId}". Cannot handle message with method "${event}".`);
|
|
191
|
-
throw new Error(`Cannot delegate keyring Snap message, keyring does not exist yet for Snap "${snapId}".`);
|
|
192
|
-
}
|
|
193
|
-
throw error;
|
|
194
|
-
}
|
|
147
|
+
const snapKeyring = await this.getLegacySnapKeyring();
|
|
148
|
+
return snapKeyring.handleKeyringSnapMessage(snapId, message);
|
|
195
149
|
}
|
|
196
150
|
}
|
|
197
151
|
exports.SnapAccountService = SnapAccountService;
|
|
198
|
-
_SnapAccountService_messenger = new WeakMap(), _SnapAccountService_watcher = new WeakMap(), _SnapAccountService_tracker = new WeakMap(),
|
|
152
|
+
_SnapAccountService_messenger = new WeakMap(), _SnapAccountService_watcher = new WeakMap(), _SnapAccountService_tracker = new WeakMap(), _SnapAccountService_instances = new WeakSet(), _SnapAccountService_handleSelectedAccountGroupChange = function _SnapAccountService_handleSelectedAccountGroupChange(groupId) {
|
|
199
153
|
__classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_forwardSelectedAccounts).call(this, groupId, __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_getAccountGroup).call(this, groupId)?.accounts);
|
|
200
154
|
}, _SnapAccountService_handleUnlock = function _SnapAccountService_handleUnlock() {
|
|
201
155
|
const groupId = __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_getSelectedAccountGroupId).call(this);
|
|
@@ -208,105 +162,6 @@ _SnapAccountService_messenger = new WeakMap(), _SnapAccountService_watcher = new
|
|
|
208
162
|
if (groupId === __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_getSelectedAccountGroupId).call(this)) {
|
|
209
163
|
__classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_forwardSelectedAccounts).call(this, groupId, []);
|
|
210
164
|
}
|
|
211
|
-
}, _SnapAccountService_migrate =
|
|
212
|
-
/**
|
|
213
|
-
* Performs the actual migration logic. Should only be called once, and is not
|
|
214
|
-
* safe to call concurrently.
|
|
215
|
-
*/
|
|
216
|
-
async function _SnapAccountService_migrate() {
|
|
217
|
-
await __classPrivateFieldGet(this, _SnapAccountService_messenger, "f").call('KeyringController:withController', async (controller) => {
|
|
218
|
-
const { keyrings } = controller;
|
|
219
|
-
const legacySnapKeyringEntry = keyrings.find(({ keyring }) => isLegacySnapKeyring(keyring));
|
|
220
|
-
if (!legacySnapKeyringEntry) {
|
|
221
|
-
(0, logger_1.projectLogger)('No legacy Snap keyring found. Migration not required.');
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
(0, logger_1.projectLogger)('Migration started...');
|
|
225
|
-
// The legacy Snap keyring has never been a true `EthKeyring` so we
|
|
226
|
-
// need to cast it to `unknown` first.
|
|
227
|
-
const legacySnapKeyring = legacySnapKeyringEntry.keyring;
|
|
228
|
-
// Compute the account list for each Snap, grouped by snap ID.
|
|
229
|
-
const states = new Map();
|
|
230
|
-
for (const internalAccount of legacySnapKeyring.listAccounts()) {
|
|
231
|
-
// Convert `InternalAccount` to `KeyringAccount` since the Snap
|
|
232
|
-
// keyring (v2) expects accounts in that format and will verify it
|
|
233
|
-
// with `superstruct` when adding the keyring.
|
|
234
|
-
const { metadata, ...account } = internalAccount;
|
|
235
|
-
const snap = metadata?.snap;
|
|
236
|
-
if (snap) {
|
|
237
|
-
const snapId = snap.id;
|
|
238
|
-
let state = states.get(snapId);
|
|
239
|
-
if (!state) {
|
|
240
|
-
state = { snapId, accounts: {} };
|
|
241
|
-
states.set(snapId, state);
|
|
242
|
-
}
|
|
243
|
-
state.accounts[account.id] = account;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// Create the new Snap keyring (v2) for each Snap and migrate the
|
|
247
|
-
// accounts over.
|
|
248
|
-
for (const state of states.values()) {
|
|
249
|
-
(0, logger_1.projectLogger)(`Migrating accounts for Snap "${state.snapId}"...`);
|
|
250
|
-
await controller.addNewKeyring(
|
|
251
|
-
// IMPORTANT: The Snap keyring (v2) can also be used as a v1
|
|
252
|
-
// keyring. So the builder associated with the v2 keyring type is
|
|
253
|
-
// able to build both v1 and v2 keyrings.
|
|
254
|
-
v2_2.KeyringType.Snap, state);
|
|
255
|
-
}
|
|
256
|
-
// Remove the legacy Snap keyring after migration.
|
|
257
|
-
(0, logger_1.projectLogger)('Removing legacy Snap keyring...');
|
|
258
|
-
await controller.removeKeyring(legacySnapKeyringEntry.metadata.id);
|
|
259
|
-
(0, logger_1.projectLogger)('Migration completed!');
|
|
260
|
-
});
|
|
261
|
-
}, _SnapAccountService_ensureKeyringIsReady =
|
|
262
|
-
/**
|
|
263
|
-
* Ensures a Snap keyring is ready for the given Snap. If it doesn't exist yet, it will be created.
|
|
264
|
-
* Safe to call concurrently.
|
|
265
|
-
*
|
|
266
|
-
* @param snapId - The Snap ID to ensure the keyring is ready for.
|
|
267
|
-
*/
|
|
268
|
-
async function _SnapAccountService_ensureKeyringIsReady(snapId) {
|
|
269
|
-
await __classPrivateFieldGet(this, _SnapAccountService_messenger, "f").call('KeyringController:withController', async (controller) => {
|
|
270
|
-
const hasKeyring = controller.keyrings.some(({ keyringV2 }) => keyringV2 &&
|
|
271
|
-
(0, v2_1.isSnapKeyring)(keyringV2) &&
|
|
272
|
-
keyringV2.snapId === snapId);
|
|
273
|
-
if (!hasKeyring) {
|
|
274
|
-
(0, logger_1.projectLogger)(`Creating v2 keyring for Snap "${snapId}"...`);
|
|
275
|
-
await controller.addNewKeyring(v2_2.KeyringType.Snap, {
|
|
276
|
-
snapId,
|
|
277
|
-
accounts: {},
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
}, _SnapAccountService_withKeyringV2Call =
|
|
282
|
-
/**
|
|
283
|
-
* Shared body for {@link SnapAccountService.#withKeyringV2} and
|
|
284
|
-
* {@link SnapAccountService.#withKeyringV2Unsafe}. Hides the per-Snap
|
|
285
|
-
* filter and the cast back to {@link SnapKeyring} (the messenger action's
|
|
286
|
-
* callback receives a generic `Keyring`; the selector's type predicate
|
|
287
|
-
* doesn't flow through the messenger's generics).
|
|
288
|
-
*
|
|
289
|
-
* @param action - The messenger action to invoke.
|
|
290
|
-
* @param snapId - The Snap ID to look up the keyring for.
|
|
291
|
-
* @param operation - The operation to run with the matching keyring.
|
|
292
|
-
* @returns The result of the operation.
|
|
293
|
-
*/
|
|
294
|
-
async function _SnapAccountService_withKeyringV2Call(action, snapId, operation) {
|
|
295
|
-
return __classPrivateFieldGet(this, _SnapAccountService_messenger, "f").call(action, {
|
|
296
|
-
filter: (keyring) => (0, v2_1.isSnapKeyring)(keyring) && keyring.snapId === snapId,
|
|
297
|
-
}, async ({ keyring }) => operation(keyring));
|
|
298
|
-
}, _SnapAccountService_withKeyringV2Unsafe =
|
|
299
|
-
/**
|
|
300
|
-
* Lock-free variant of {@link SnapAccountService.#withKeyringV2}. Only use
|
|
301
|
-
* for operations that do not mutate keyring or controller state — see
|
|
302
|
-
* `KeyringController.withKeyringV2Unsafe` for the contract.
|
|
303
|
-
*
|
|
304
|
-
* @param snapId - The Snap ID to look up the keyring for.
|
|
305
|
-
* @param operation - The operation to run with the matching keyring.
|
|
306
|
-
* @returns The result of the operation.
|
|
307
|
-
*/
|
|
308
|
-
async function _SnapAccountService_withKeyringV2Unsafe(snapId, operation) {
|
|
309
|
-
return __classPrivateFieldGet(this, _SnapAccountService_instances, "m", _SnapAccountService_withKeyringV2Call).call(this, 'KeyringController:withKeyringV2Unsafe', snapId, operation);
|
|
310
165
|
}, _SnapAccountService_forwardSelectedAccounts = function _SnapAccountService_forwardSelectedAccounts(groupId, accounts) {
|
|
311
166
|
if (!groupId) {
|
|
312
167
|
(0, logger_1.projectLogger)('No selected account group, skipping forwarding selected accounts to Snap keyring.');
|
|
@@ -316,38 +171,17 @@ async function _SnapAccountService_withKeyringV2Unsafe(snapId, operation) {
|
|
|
316
171
|
(0, logger_1.projectLogger)(`Account group ("${groupId}") has no accounts, skipping forwarding selected accounts to Snap keyring.`);
|
|
317
172
|
return;
|
|
318
173
|
}
|
|
319
|
-
if (accounts.length) {
|
|
320
|
-
(0, logger_1.projectLogger)(`Forwarding selected accounts (from "${groupId}"): ${accounts.join(', ')}`);
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
(0, logger_1.projectLogger)(`Clearing selected accounts (from "${groupId}")`);
|
|
324
|
-
}
|
|
325
174
|
const forwardSelectedAccounts = async () => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// subset still gets forwarded to explicitly clear the
|
|
335
|
-
// Snap selected accounts.
|
|
336
|
-
await keyring.setSelectedAccounts(accounts.filter((id) => keyring.hasAccount(id)));
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
// Tracked Snaps without a v2 keyring yet are expected —
|
|
341
|
-
// forwarding will resume on the next event once `ensureReady`
|
|
342
|
-
// has run.
|
|
343
|
-
if (!(0, keyring_controller_1.isKeyringNotFoundError)(error)) {
|
|
344
|
-
console.error(`Error forwarding selected accounts to Snap "${snapId}":`, error);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}));
|
|
175
|
+
if (accounts.length) {
|
|
176
|
+
(0, logger_1.projectLogger)(`Forwarding selected accounts (from "${groupId}"): ${accounts.join(', ')}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
(0, logger_1.projectLogger)(`Clearing selected accounts (from "${groupId}")`);
|
|
180
|
+
}
|
|
181
|
+
const snapKeyring = await this.getLegacySnapKeyring();
|
|
182
|
+
await snapKeyring.setSelectedAccounts(accounts);
|
|
348
183
|
};
|
|
349
|
-
// There is nothing we can do if forwarding fails. This will auto-recover on
|
|
350
|
-
// the next relevant event.
|
|
184
|
+
// There is nothing we can do if forwarding fails. This will auto-recover on the next relevant event.
|
|
351
185
|
forwardSelectedAccounts().catch((error) => {
|
|
352
186
|
console.error('Error forwarding selected accounts:', error);
|
|
353
187
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapAccountService.cjs","sourceRoot":"","sources":["../src/SnapAccountService.ts"],"names":[],"mappings":"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","sourcesContent":["import { AccountGroupId } from '@metamask/account-api';\nimport type {\n SnapKeyring as LegacySnapKeyring,\n SnapMessage,\n} from '@metamask/eth-snap-keyring';\nimport {\n SnapKeyring,\n SnapKeyringState,\n isSnapKeyring,\n} from '@metamask/eth-snap-keyring/v2';\nimport { KeyringEvent } from '@metamask/keyring-api';\nimport { KeyringType } from '@metamask/keyring-api/v2';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerStateChangeEvent,\n KeyringControllerUnlockEvent,\n KeyringControllerWithControllerAction,\n KeyringControllerWithKeyringV2Action,\n KeyringControllerWithKeyringV2UnsafeAction,\n} from '@metamask/keyring-controller';\nimport {\n isKeyringNotFoundError,\n KeyringTypes,\n} from '@metamask/keyring-controller';\nimport { SnapManageAccountsMethod } from '@metamask/keyring-snap-sdk';\nimport type { AccountId, BaseKeyring } from '@metamask/keyring-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n SnapControllerGetRunnableSnapsAction,\n SnapControllerGetSnapAction,\n SnapControllerGetStateAction,\n SnapControllerSnapBlockedEvent,\n SnapControllerSnapDisabledEvent,\n SnapControllerSnapEnabledEvent,\n SnapControllerSnapInstalledEvent,\n SnapControllerSnapUnblockedEvent,\n SnapControllerSnapUninstalledEvent,\n SnapControllerStateChangeEvent,\n} from '@metamask/snaps-controllers';\nimport { SnapId } from '@metamask/snaps-sdk';\nimport type { Json } from '@metamask/utils';\n\nimport { projectLogger as log } from './logger';\nimport type {\n SnapAccountServiceEnsureReadyAction,\n SnapAccountServiceGetSnapsAction,\n SnapAccountServiceHandleKeyringSnapMessageAction,\n SnapAccountServiceMigrateAction,\n} from './SnapAccountService-method-action-types';\nimport { SnapPlatformWatcher } from './SnapPlatformWatcher';\nimport type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher';\nimport { SnapTracker } from './SnapTracker';\nimport type {\n AccountTreeControllerGetAccountGroupObjectAction,\n AccountTreeControllerGetSelectedAccountGroupAction,\n AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerAccountGroupCreatedEvent,\n AccountTreeControllerAccountGroupUpdatedEvent,\n AccountTreeControllerAccountGroupRemovedEvent,\n AccountGroupObject,\n} from './types';\n\n/**\n * The name of the {@link SnapAccountService}, used to namespace the service's\n * actions and events.\n */\nexport const serviceName = 'SnapAccountService';\n\n/**\n * All of the methods within {@link SnapAccountService} that are exposed via\n * the messenger.\n */\nconst MESSENGER_EXPOSED_METHODS = [\n 'ensureReady',\n 'getSnaps',\n 'handleKeyringSnapMessage',\n 'migrate',\n] as const;\n\n/**\n * Actions that {@link SnapAccountService} exposes to other consumers.\n */\nexport type SnapAccountServiceActions =\n | SnapAccountServiceEnsureReadyAction\n | SnapAccountServiceGetSnapsAction\n | SnapAccountServiceHandleKeyringSnapMessageAction\n | SnapAccountServiceMigrateAction;\n\n/**\n * Actions from other messengers that {@link SnapAccountService} calls.\n */\ntype AllowedActions =\n | SnapControllerGetStateAction\n | SnapControllerGetSnapAction\n | SnapControllerGetRunnableSnapsAction\n | KeyringControllerGetStateAction\n | KeyringControllerWithControllerAction\n | KeyringControllerWithKeyringV2Action\n | KeyringControllerWithKeyringV2UnsafeAction\n | AccountTreeControllerGetAccountGroupObjectAction\n | AccountTreeControllerGetSelectedAccountGroupAction;\n\n/**\n * Events that {@link SnapAccountService} exposes to other consumers.\n */\nexport type SnapAccountServiceEvents = never;\n\n/**\n * Events from other messengers that {@link SnapAccountService} subscribes to.\n */\ntype AllowedEvents =\n | SnapControllerStateChangeEvent\n | SnapControllerSnapInstalledEvent\n | SnapControllerSnapEnabledEvent\n | SnapControllerSnapDisabledEvent\n | SnapControllerSnapBlockedEvent\n | SnapControllerSnapUnblockedEvent\n | SnapControllerSnapUninstalledEvent\n | KeyringControllerStateChangeEvent\n | KeyringControllerUnlockEvent\n | AccountTreeControllerSelectedAccountGroupChangeEvent\n | AccountTreeControllerAccountGroupCreatedEvent\n | AccountTreeControllerAccountGroupUpdatedEvent\n | AccountTreeControllerAccountGroupRemovedEvent;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link SnapAccountService}.\n */\nexport type SnapAccountServiceMessenger = Messenger<\n typeof serviceName,\n SnapAccountServiceActions | AllowedActions,\n SnapAccountServiceEvents | AllowedEvents\n>;\n\n/**\n * Configuration for the {@link SnapAccountService}.\n */\nexport type SnapAccountServiceConfig = {\n snapPlatformWatcher?: SnapPlatformWatcherConfig;\n};\n\n/**\n * The options that {@link SnapAccountService} takes.\n */\nexport type SnapAccountServiceOptions = {\n messenger: SnapAccountServiceMessenger;\n config?: SnapAccountServiceConfig;\n};\n\n/**\n * Checks if a given keyring is a Snap keyring (v2).\n *\n * @param keyring - The keyring to check.\n * @param keyring.type - The type of the keyring.\n * @returns `true` if the keyring is a Snap keyring (v2), `false` otherwise.\n */\nfunction isLegacySnapKeyring(keyring: {\n type: BaseKeyring['type'];\n}): keyring is LegacySnapKeyring {\n return keyring.type === KeyringTypes.snap;\n}\n\n/**\n * Service responsible for managing account management snaps.\n */\nexport class SnapAccountService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: SnapAccountServiceMessenger;\n\n readonly #watcher: SnapPlatformWatcher;\n\n readonly #tracker: SnapTracker;\n\n #migrated = false;\n\n #migratePromise: Promise<void> | null = null;\n\n /**\n * Constructs a new {@link SnapAccountService}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.config - Optional service configuration.\n */\n constructor({ messenger, config }: SnapAccountServiceOptions) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#watcher = new SnapPlatformWatcher(\n messenger,\n config?.snapPlatformWatcher,\n );\n this.#tracker = new SnapTracker(messenger);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:selectedAccountGroupChange',\n (groupId) => this.#handleSelectedAccountGroupChange(groupId),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupCreated',\n (group) => this.#handleAccountGroupCreatedOrUpdated(group),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupUpdated',\n (group) => this.#handleAccountGroupCreatedOrUpdated(group),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupRemoved',\n (groupId) => this.#handleAccountGroupRemoved(groupId),\n );\n\n this.#messenger.subscribe('KeyringController:unlock', () =>\n this.#handleUnlock(),\n );\n }\n\n /**\n * Handles changes to the selected account group by forwarding the new\n * group's accounts to the Snap keyring.\n *\n * @param groupId - The ID of the newly selected account group.\n */\n #handleSelectedAccountGroupChange(groupId: AccountGroupId | ''): void {\n this.#forwardSelectedAccounts(\n groupId,\n this.#getAccountGroup(groupId)?.accounts,\n );\n }\n\n /**\n * Handles the keyring controller unlock event by forwarding the currently\n * selected account group's accounts to the Snap keyring.\n */\n #handleUnlock(): void {\n const groupId = this.#getSelectedAccountGroupId();\n this.#forwardSelectedAccounts(\n groupId,\n this.#getAccountGroup(groupId)?.accounts,\n );\n }\n\n /**\n * Handles created or updated account groups by forwarding the accounts of the currently\n * selected account group to the Snap keyring, if the created/updated group is currently selected.\n *\n * @param group - The account group being created or updated.\n */\n #handleAccountGroupCreatedOrUpdated(group: AccountGroupObject): void {\n if (group.id === this.#getSelectedAccountGroupId()) {\n this.#forwardSelectedAccounts(group.id, group.accounts);\n }\n }\n\n /**\n * Handles removed account groups by forwarding the accounts of the currently\n * selected account group to the Snap keyring, if the removed group is currently selected.\n *\n * @param groupId - The ID of the account group being removed.\n */\n #handleAccountGroupRemoved(groupId: AccountGroupId): void {\n if (groupId === this.#getSelectedAccountGroupId()) {\n this.#forwardSelectedAccounts(\n groupId,\n [], // Clearing accounts since the group is removed\n );\n }\n }\n\n /**\n * Initializes the snap account service.\n *\n * Seeds the internal set of account-management Snaps from\n * `SnapController:getRunnableSnaps`, then starts processing lifecycle\n * events.\n */\n async init(): Promise<void> {\n await this.#tracker.init();\n }\n\n /**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\n getSnaps(): SnapId[] {\n return this.#tracker.getSnaps();\n }\n\n /**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2. Runs the legacy -> v2 Snap keyring migration (cached — no-op if\n * already done).\n * 3. Atomically creates the v2 keyring for this Snap if it doesn't exist\n * yet.\n * 4. Waits for the Snap platform to be fully started.\n *\n * Safe to call concurrently — each step is idempotent or mutex-protected.\n *\n * @param snapId - ID of the Snap to ensure readiness for.\n * @throws If `snapId` is not a tracked account-management Snap.\n */\n async ensureReady(snapId: SnapId): Promise<void> {\n if (!this.#tracker.canUse(snapId)) {\n throw new Error(`Unknown snap: \"${snapId}\"`);\n }\n\n // Migrate from the global v1 Snap keyring to the per-Snap v2 keyring\n // before doing anything else.\n await this.migrate();\n\n // We still try to create the keyring for the Snap here, since we might\n // want to use a new Snap that never had accounts before.\n await this.#ensureKeyringIsReady(snapId);\n\n // Before doing anything with our Snap, we need to make sure the platform\n // is ready to process requests.\n await this.#watcher.ensureCanUseSnapPlatform();\n }\n\n /**\n * Migrate the legacy Snap keyring to the new (per-snap) Snap keyring v2.\n * Safe to call concurrently — the migration runs only once; all callers\n * await the same promise.\n *\n * @returns A promise that resolves when the migration is complete.\n */\n async migrate(): Promise<void> {\n if (this.#migrated) {\n return;\n }\n if (!this.#migratePromise) {\n this.#migratePromise = this.#migrate();\n\n try {\n await this.#migratePromise;\n\n // Only mark it as migrated after the migration logic completes successfully. If\n // it fails, we want future calls to retry the migration.\n this.#migrated = true;\n } finally {\n this.#migratePromise = null;\n }\n }\n await this.#migratePromise;\n }\n\n /**\n * Performs the actual migration logic. Should only be called once, and is not\n * safe to call concurrently.\n */\n async #migrate(): Promise<void> {\n await this.#messenger.call(\n 'KeyringController:withController',\n async (controller) => {\n const { keyrings } = controller;\n\n const legacySnapKeyringEntry = keyrings.find(({ keyring }) =>\n isLegacySnapKeyring(keyring),\n );\n if (!legacySnapKeyringEntry) {\n log('No legacy Snap keyring found. Migration not required.');\n return;\n }\n\n log('Migration started...');\n\n // The legacy Snap keyring has never been a true `EthKeyring` so we\n // need to cast it to `unknown` first.\n const legacySnapKeyring =\n legacySnapKeyringEntry.keyring as unknown as LegacySnapKeyring;\n\n // Compute the account list for each Snap, grouped by snap ID.\n const states = new Map<SnapId, SnapKeyringState>();\n for (const internalAccount of legacySnapKeyring.listAccounts()) {\n // Convert `InternalAccount` to `KeyringAccount` since the Snap\n // keyring (v2) expects accounts in that format and will verify it\n // with `superstruct` when adding the keyring.\n const { metadata, ...account } = internalAccount;\n\n const snap = metadata?.snap;\n if (snap) {\n const snapId = snap.id as SnapId;\n\n let state = states.get(snapId);\n if (!state) {\n state = { snapId, accounts: {} };\n states.set(snapId, state);\n }\n state.accounts[account.id] = account;\n }\n }\n\n // Create the new Snap keyring (v2) for each Snap and migrate the\n // accounts over.\n for (const state of states.values()) {\n log(`Migrating accounts for Snap \"${state.snapId}\"...`);\n await controller.addNewKeyring(\n // IMPORTANT: The Snap keyring (v2) can also be used as a v1\n // keyring. So the builder associated with the v2 keyring type is\n // able to build both v1 and v2 keyrings.\n KeyringType.Snap,\n state,\n );\n }\n\n // Remove the legacy Snap keyring after migration.\n log('Removing legacy Snap keyring...');\n await controller.removeKeyring(legacySnapKeyringEntry.metadata.id);\n\n log('Migration completed!');\n },\n );\n }\n\n /**\n * Ensures a Snap keyring is ready for the given Snap. If it doesn't exist yet, it will be created.\n * Safe to call concurrently.\n *\n * @param snapId - The Snap ID to ensure the keyring is ready for.\n */\n async #ensureKeyringIsReady(snapId: SnapId): Promise<void> {\n await this.#messenger.call(\n 'KeyringController:withController',\n async (controller) => {\n const hasKeyring = controller.keyrings.some(\n ({ keyringV2 }) =>\n keyringV2 &&\n isSnapKeyring(keyringV2) &&\n keyringV2.snapId === snapId,\n );\n\n if (!hasKeyring) {\n log(`Creating v2 keyring for Snap \"${snapId}\"...`);\n await controller.addNewKeyring(KeyringType.Snap, {\n snapId,\n accounts: {},\n });\n }\n },\n );\n }\n\n /**\n * Shared body for {@link SnapAccountService.#withKeyringV2} and\n * {@link SnapAccountService.#withKeyringV2Unsafe}. Hides the per-Snap\n * filter and the cast back to {@link SnapKeyring} (the messenger action's\n * callback receives a generic `Keyring`; the selector's type predicate\n * doesn't flow through the messenger's generics).\n *\n * @param action - The messenger action to invoke.\n * @param snapId - The Snap ID to look up the keyring for.\n * @param operation - The operation to run with the matching keyring.\n * @returns The result of the operation.\n */\n async #withKeyringV2Call<Result>(\n action:\n | 'KeyringController:withKeyringV2'\n | 'KeyringController:withKeyringV2Unsafe',\n snapId: SnapId,\n operation: (keyring: SnapKeyring) => Promise<Result>,\n ): Promise<Result> {\n return this.#messenger.call(\n action,\n {\n filter: (keyring): keyring is SnapKeyring =>\n isSnapKeyring(keyring) && keyring.snapId === snapId,\n },\n async ({ keyring }) => operation(keyring as SnapKeyring),\n ) as Result;\n }\n\n /**\n * Lock-free variant of {@link SnapAccountService.#withKeyringV2}. Only use\n * for operations that do not mutate keyring or controller state — see\n * `KeyringController.withKeyringV2Unsafe` for the contract.\n *\n * @param snapId - The Snap ID to look up the keyring for.\n * @param operation - The operation to run with the matching keyring.\n * @returns The result of the operation.\n */\n async #withKeyringV2Unsafe<Result>(\n snapId: SnapId,\n operation: (keyring: SnapKeyring) => Promise<Result>,\n ): Promise<Result> {\n return this.#withKeyringV2Call(\n 'KeyringController:withKeyringV2Unsafe',\n snapId,\n operation,\n );\n }\n\n /**\n * Handle a message from a Snap.\n *\n * @param snapId - ID of the Snap.\n * @param message - Message sent by the Snap.\n * @returns The execution result.\n */\n async handleKeyringSnapMessage(\n snapId: SnapId,\n message: SnapMessage,\n ): Promise<Json> {\n // Handle specific methods first.\n if (message.method === SnapManageAccountsMethod.GetSelectedAccounts) {\n const groupId = this.#getSelectedAccountGroupId();\n const accounts = this.#getAccountGroup(groupId)?.accounts ?? [];\n\n return await this.#withKeyringV2Unsafe(snapId, async (keyring) =>\n accounts.filter((id) => keyring.hasAccount(id)),\n );\n }\n\n const event = message.method as KeyringEvent; // We assume the Snap platform always sends a valid `KeyringEvent` here.\n log(\n `Forwarding message \"${event}\" from Snap \"${snapId}\" to its keyring...`,\n );\n\n // We can create a new keyring if the message is an AccountCreated event.\n const isAccountCreatedMessage = event === KeyringEvent.AccountCreated;\n\n // Create the Snap keyring if it doesn't exist yet (in an atomic way). We cannot assume\n // the keyring exists (e.g for the MMI Snap).\n // NOTE: We only auto-create it for v1 account creation flows.\n if (isAccountCreatedMessage) {\n await this.#ensureKeyringIsReady(snapId);\n }\n\n // This part of the flow relies on v1 flows, but v2 keyrings are compatible with those messages\n // too.\n try {\n // NOTE: We use \"unsafe\" here since none of the messages should trigger mutations to the keyring state.\n // The exception might be `:accountCreated`, but even in that case, the mutation is handled differently\n // in the client by call `:persistAllKeyrings` explicitly.\n // Using `:withKeyringV2` would cause a deadlock when we're initiating operations like `removeAccount` from\n // the keyring itself:\n // 1: withKeyring(..., ({ keyring }) => { keyring.removeAccount(...) })\n // 2. removeAccount(...) -> handleKeyringSnapMessage(..., { method: 'accountRemoved', ... })\n // 3. handleKeyringSnapMessage tries to acquire the same lock again via withKeyringV2 -> deadlock.\n return await this.#withKeyringV2Unsafe(snapId, async (keyring) =>\n keyring.handleKeyringSnapMessage(message),\n );\n } catch (error) {\n if (isKeyringNotFoundError(error)) {\n log(\n `No Snap keyring found for Snap \"${snapId}\". Cannot handle message with method \"${event}\".`,\n );\n\n throw new Error(\n `Cannot delegate keyring Snap message, keyring does not exist yet for Snap \"${snapId}\".`,\n );\n }\n\n throw error;\n }\n }\n\n /**\n * Forwards the accounts of the given account group to the Snap keyring.\n *\n * @param groupId - The ID of the account group whose accounts should be\n * forwarded. If empty, this is a no-op.\n * @param accounts - The accounts to forward. If not defined, this is a no-op.\n */\n #forwardSelectedAccounts(\n groupId: AccountGroupId | '',\n accounts: AccountId[] | undefined,\n ): void {\n if (!groupId) {\n log(\n 'No selected account group, skipping forwarding selected accounts to Snap keyring.',\n );\n return;\n }\n\n if (!accounts) {\n log(\n `Account group (\"${groupId}\") has no accounts, skipping forwarding selected accounts to Snap keyring.`,\n );\n return;\n }\n\n if (accounts.length) {\n log(\n `Forwarding selected accounts (from \"${groupId}\"): ${accounts.join(', ')}`,\n );\n } else {\n log(`Clearing selected accounts (from \"${groupId}\")`);\n }\n\n const forwardSelectedAccounts = async (): Promise<void> => {\n await Promise.all(\n this.#tracker.getSnaps().map(async (snapId) => {\n try {\n // We can safely invoke this method without taking the controller lock\n // because it should not mutate the keyring state. So we can use\n // `withKeyringV2Unsafe` in this case.\n await this.#withKeyringV2Unsafe(snapId, async (keyring) => {\n // The group's accounts may belong to several Snaps; only\n // forward the subset this Snap actually owns. An empty\n // subset still gets forwarded to explicitly clear the\n // Snap selected accounts.\n await keyring.setSelectedAccounts(\n accounts.filter((id) => keyring.hasAccount(id)),\n );\n });\n } catch (error) {\n // Tracked Snaps without a v2 keyring yet are expected —\n // forwarding will resume on the next event once `ensureReady`\n // has run.\n if (!isKeyringNotFoundError(error)) {\n console.error(\n `Error forwarding selected accounts to Snap \"${snapId}\":`,\n error,\n );\n }\n }\n }),\n );\n };\n\n // There is nothing we can do if forwarding fails. This will auto-recover on\n // the next relevant event.\n forwardSelectedAccounts().catch((error) => {\n console.error('Error forwarding selected accounts:', error);\n });\n }\n\n /**\n * Gets the account group object for the given group ID.\n *\n * @param groupId - The ID of the account group.\n * @returns The account group object, or undefined if the group ID is empty or the group does not exist.\n */\n #getAccountGroup(\n groupId: AccountGroupId | '',\n ): AccountGroupObject | undefined {\n if (!groupId) {\n return undefined;\n }\n\n return this.#messenger.call(\n 'AccountTreeController:getAccountGroupObject',\n groupId,\n );\n }\n\n /**\n * Gets the currently selected account group ID.\n *\n * @returns The currently selected account group ID, or an empty string if\n * there is no selected account group.\n */\n #getSelectedAccountGroupId(): AccountGroupId | '' {\n return this.#messenger.call(\n 'AccountTreeController:getSelectedAccountGroup',\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SnapAccountService.cjs","sourceRoot":"","sources":["../src/SnapAccountService.ts"],"names":[],"mappings":"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","sourcesContent":["import { AccountGroupId } from '@metamask/account-api';\nimport type {\n SnapKeyring as LegacySnapKeyring,\n SnapMessage,\n} from '@metamask/eth-snap-keyring';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerStateChangeEvent,\n KeyringControllerUnlockEvent,\n KeyringControllerWithControllerAction,\n KeyringEntry,\n} from '@metamask/keyring-controller';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type { AccountId, BaseKeyring } from '@metamask/keyring-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n SnapControllerGetRunnableSnapsAction,\n SnapControllerGetSnapAction,\n SnapControllerGetStateAction,\n SnapControllerSnapBlockedEvent,\n SnapControllerSnapDisabledEvent,\n SnapControllerSnapEnabledEvent,\n SnapControllerSnapInstalledEvent,\n SnapControllerSnapUnblockedEvent,\n SnapControllerSnapUninstalledEvent,\n SnapControllerStateChangeEvent,\n} from '@metamask/snaps-controllers';\nimport { SnapId } from '@metamask/snaps-sdk';\nimport type { Json } from '@metamask/utils';\n\nimport { projectLogger as log } from './logger';\nimport type {\n SnapAccountServiceEnsureReadyAction,\n SnapAccountServiceGetLegacySnapKeyringAction,\n SnapAccountServiceGetSnapsAction,\n SnapAccountServiceHandleKeyringSnapMessageAction,\n} from './SnapAccountService-method-action-types';\nimport { SnapPlatformWatcher } from './SnapPlatformWatcher';\nimport type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher';\nimport { SnapTracker } from './SnapTracker';\nimport type {\n AccountTreeControllerGetAccountGroupObjectAction,\n AccountTreeControllerGetSelectedAccountGroupAction,\n AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerAccountGroupCreatedEvent,\n AccountTreeControllerAccountGroupUpdatedEvent,\n AccountTreeControllerAccountGroupRemovedEvent,\n AccountGroupObject,\n} from './types';\n\n/**\n * The name of the {@link SnapAccountService}, used to namespace the service's\n * actions and events.\n */\nexport const serviceName = 'SnapAccountService';\n\n/**\n * All of the methods within {@link SnapAccountService} that are exposed via\n * the messenger.\n */\nconst MESSENGER_EXPOSED_METHODS = [\n 'ensureReady',\n 'getSnaps',\n 'getLegacySnapKeyring',\n 'handleKeyringSnapMessage',\n] as const;\n\n/**\n * Actions that {@link SnapAccountService} exposes to other consumers.\n */\nexport type SnapAccountServiceActions =\n | SnapAccountServiceEnsureReadyAction\n | SnapAccountServiceGetSnapsAction\n | SnapAccountServiceGetLegacySnapKeyringAction\n | SnapAccountServiceHandleKeyringSnapMessageAction;\n\n/**\n * Actions from other messengers that {@link SnapAccountService} calls.\n */\ntype AllowedActions =\n | SnapControllerGetStateAction\n | SnapControllerGetSnapAction\n | SnapControllerGetRunnableSnapsAction\n | KeyringControllerGetStateAction\n | KeyringControllerWithControllerAction\n | AccountTreeControllerGetAccountGroupObjectAction\n | AccountTreeControllerGetSelectedAccountGroupAction;\n\n/**\n * Events that {@link SnapAccountService} exposes to other consumers.\n */\nexport type SnapAccountServiceEvents = never;\n\n/**\n * Events from other messengers that {@link SnapAccountService} subscribes to.\n */\ntype AllowedEvents =\n | SnapControllerStateChangeEvent\n | SnapControllerSnapInstalledEvent\n | SnapControllerSnapEnabledEvent\n | SnapControllerSnapDisabledEvent\n | SnapControllerSnapBlockedEvent\n | SnapControllerSnapUnblockedEvent\n | SnapControllerSnapUninstalledEvent\n | KeyringControllerStateChangeEvent\n | KeyringControllerUnlockEvent\n | AccountTreeControllerSelectedAccountGroupChangeEvent\n | AccountTreeControllerAccountGroupCreatedEvent\n | AccountTreeControllerAccountGroupUpdatedEvent\n | AccountTreeControllerAccountGroupRemovedEvent;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link SnapAccountService}.\n */\nexport type SnapAccountServiceMessenger = Messenger<\n typeof serviceName,\n SnapAccountServiceActions | AllowedActions,\n SnapAccountServiceEvents | AllowedEvents\n>;\n\n/**\n * Configuration for the {@link SnapAccountService}.\n */\nexport type SnapAccountServiceConfig = {\n snapPlatformWatcher?: SnapPlatformWatcherConfig;\n};\n\n/**\n * The options that {@link SnapAccountService} takes.\n */\nexport type SnapAccountServiceOptions = {\n messenger: SnapAccountServiceMessenger;\n config?: SnapAccountServiceConfig;\n};\n\n/**\n * Checks if a given keyring is a Snap keyring (v2).\n *\n * @param keyring - The keyring to check.\n * @param keyring.type - The type of the keyring.\n * @returns `true` if the keyring is a Snap keyring (v2), `false` otherwise.\n */\nfunction isLegacySnapKeyring(keyring: {\n type: BaseKeyring['type'];\n}): keyring is LegacySnapKeyring {\n return keyring.type === KeyringTypes.snap;\n}\n\n/**\n * Service responsible for managing account management snaps.\n */\nexport class SnapAccountService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: SnapAccountServiceMessenger;\n\n readonly #watcher: SnapPlatformWatcher;\n\n readonly #tracker: SnapTracker;\n\n /**\n * Constructs a new {@link SnapAccountService}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.config - Optional service configuration.\n */\n constructor({ messenger, config }: SnapAccountServiceOptions) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#watcher = new SnapPlatformWatcher(\n messenger,\n config?.snapPlatformWatcher,\n );\n this.#tracker = new SnapTracker(messenger);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:selectedAccountGroupChange',\n (groupId) => this.#handleSelectedAccountGroupChange(groupId),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupCreated',\n (group) => this.#handleAccountGroupCreatedOrUpdated(group),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupUpdated',\n (group) => this.#handleAccountGroupCreatedOrUpdated(group),\n );\n\n this.#messenger.subscribe(\n 'AccountTreeController:accountGroupRemoved',\n (groupId) => this.#handleAccountGroupRemoved(groupId),\n );\n\n this.#messenger.subscribe('KeyringController:unlock', () =>\n this.#handleUnlock(),\n );\n }\n\n /**\n * Handles changes to the selected account group by forwarding the new\n * group's accounts to the Snap keyring.\n *\n * @param groupId - The ID of the newly selected account group.\n */\n #handleSelectedAccountGroupChange(groupId: AccountGroupId | ''): void {\n this.#forwardSelectedAccounts(\n groupId,\n this.#getAccountGroup(groupId)?.accounts,\n );\n }\n\n /**\n * Handles the keyring controller unlock event by forwarding the currently\n * selected account group's accounts to the Snap keyring.\n */\n #handleUnlock(): void {\n const groupId = this.#getSelectedAccountGroupId();\n this.#forwardSelectedAccounts(\n groupId,\n this.#getAccountGroup(groupId)?.accounts,\n );\n }\n\n /**\n * Handles created or updated account groups by forwarding the accounts of the currently\n * selected account group to the Snap keyring, if the created/updated group is currently selected.\n *\n * @param group - The account group being created or updated.\n */\n #handleAccountGroupCreatedOrUpdated(group: AccountGroupObject): void {\n if (group.id === this.#getSelectedAccountGroupId()) {\n this.#forwardSelectedAccounts(group.id, group.accounts);\n }\n }\n\n /**\n * Handles removed account groups by forwarding the accounts of the currently\n * selected account group to the Snap keyring, if the removed group is currently selected.\n *\n * @param groupId - The ID of the account group being removed.\n */\n #handleAccountGroupRemoved(groupId: AccountGroupId): void {\n if (groupId === this.#getSelectedAccountGroupId()) {\n this.#forwardSelectedAccounts(\n groupId,\n [], // Clearing accounts since the group is removed\n );\n }\n }\n\n /**\n * Initializes the snap account service.\n *\n * Seeds the internal set of account-management Snaps from\n * `SnapController:getRunnableSnaps`, then starts processing lifecycle\n * events.\n */\n async init(): Promise<void> {\n await this.#tracker.init();\n }\n\n /**\n * Returns the IDs of all currently tracked account-management Snaps —\n * Snaps that are installed, enabled, not blocked, and have the\n * `endowment:keyring` permission.\n *\n * @returns The IDs of tracked account-management Snaps.\n */\n getSnaps(): SnapId[] {\n return this.#tracker.getSnaps();\n }\n\n /**\n * Ensures everything is ready to use Snap accounts for the given Snap.\n * 1. Validates that `snapId` is a tracked account-management Snap.\n * 2. Waits for the Snap platform to be fully started.\n *\n * Safe to call concurrently — each step is idempotent or mutex-protected.\n *\n * @param snapId - ID of the Snap to ensure readiness for.\n * @throws If `snapId` is not a tracked account-management Snap.\n */\n async ensureReady(snapId: SnapId): Promise<void> {\n if (!this.#tracker.canUse(snapId)) {\n throw new Error(`Unknown snap: \"${snapId}\"`);\n }\n // Before doing anything with our Snap, we need to make sure the platform\n // is ready to process requests.\n await this.#watcher.ensureCanUseSnapPlatform();\n }\n\n /**\n * Atomically gets-or-creates the legacy (v1) Snap keyring — the keyring\n * associated with {@link KeyringTypes.snap}.\n *\n * @returns The existing or newly-created Snap keyring instance.\n */\n async getLegacySnapKeyring(): Promise<LegacySnapKeyring> {\n type Result = {\n snapKeyring: LegacySnapKeyring;\n };\n\n // `KeyringController:withController` forbids returning a direct keyring\n // reference (it checks the result via `Object.is`), so we smuggle the\n // instance out wrapped in an object and unwrap it after the call.\n // NOTE: This violates the abstraction of `KeyringController:withController`, but this\n // is how we currently interact with the legacy Snap keyring. Once we migrate it to\n // the Snap keyring v2, we won't be using the same pattern.\n const result = await this.#messenger.call(\n 'KeyringController:withController',\n async (controller): Promise<Result> => {\n let snapKeyring: KeyringEntry['keyring'] | undefined;\n\n const found = controller.keyrings.find(({ keyring }) =>\n isLegacySnapKeyring(keyring),\n );\n if (found) {\n snapKeyring = found.keyring;\n }\n\n if (!snapKeyring) {\n const {\n keyring: newSnapKeyring,\n metadata: { id },\n } = await controller.addNewKeyring(KeyringTypes.snap);\n snapKeyring = newSnapKeyring;\n\n log(`Legacy Snap keyring created. (\"${id}\")`);\n }\n\n // The legacy Snap keyring is not compatible with `EthKeyring`, so we need to cast here.\n return { snapKeyring } as unknown as Result;\n },\n );\n\n return (result as Result).snapKeyring;\n }\n\n /**\n * Handle a message from a Snap.\n *\n * @param snapId - ID of the Snap.\n * @param message - Message sent by the Snap.\n * @returns The execution result.\n */\n async handleKeyringSnapMessage(\n snapId: SnapId,\n message: SnapMessage,\n ): Promise<Json> {\n const snapKeyring = await this.getLegacySnapKeyring();\n return snapKeyring.handleKeyringSnapMessage(snapId, message);\n }\n\n /**\n * Forwards the accounts of the given account group to the Snap keyring.\n *\n * @param groupId - The ID of the account group whose accounts should be\n * forwarded. If empty, this is a no-op.\n * @param accounts - The accounts to forward. If not defined, this is a no-op.\n */\n #forwardSelectedAccounts(\n groupId: AccountGroupId | '',\n accounts: AccountId[] | undefined,\n ): void {\n if (!groupId) {\n log(\n 'No selected account group, skipping forwarding selected accounts to Snap keyring.',\n );\n return;\n }\n\n if (!accounts) {\n log(\n `Account group (\"${groupId}\") has no accounts, skipping forwarding selected accounts to Snap keyring.`,\n );\n return;\n }\n\n const forwardSelectedAccounts = async (): Promise<void> => {\n if (accounts.length) {\n log(\n `Forwarding selected accounts (from \"${groupId}\"): ${accounts.join(', ')}`,\n );\n } else {\n log(`Clearing selected accounts (from \"${groupId}\")`);\n }\n\n const snapKeyring = await this.getLegacySnapKeyring();\n await snapKeyring.setSelectedAccounts(accounts);\n };\n\n // There is nothing we can do if forwarding fails. This will auto-recover on the next relevant event.\n forwardSelectedAccounts().catch((error) => {\n console.error('Error forwarding selected accounts:', error);\n });\n }\n\n /**\n * Gets the account group object for the given group ID.\n *\n * @param groupId - The ID of the account group.\n * @returns The account group object, or undefined if the group ID is empty or the group does not exist.\n */\n #getAccountGroup(\n groupId: AccountGroupId | '',\n ): AccountGroupObject | undefined {\n if (!groupId) {\n return undefined;\n }\n\n return this.#messenger.call(\n 'AccountTreeController:getAccountGroupObject',\n groupId,\n );\n }\n\n /**\n * Gets the currently selected account group ID.\n *\n * @returns The currently selected account group ID, or an empty string if\n * there is no selected account group.\n */\n #getSelectedAccountGroupId(): AccountGroupId | '' {\n return this.#messenger.call(\n 'AccountTreeController:getSelectedAccountGroup',\n );\n }\n}\n"]}
|