@metamask-previews/notification-services-controller 26.0.1-preview-ea6a28c61 → 26.0.1-preview-b63fbdb38

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 (24) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/NotificationServicesController/NotificationServicesController.cjs +137 -112
  3. package/dist/NotificationServicesController/NotificationServicesController.cjs.map +1 -1
  4. package/dist/NotificationServicesController/NotificationServicesController.d.cts.map +1 -1
  5. package/dist/NotificationServicesController/NotificationServicesController.d.mts.map +1 -1
  6. package/dist/NotificationServicesController/NotificationServicesController.mjs +138 -113
  7. package/dist/NotificationServicesController/NotificationServicesController.mjs.map +1 -1
  8. package/dist/NotificationServicesController/mocks/mockResponses.cjs +9 -1
  9. package/dist/NotificationServicesController/mocks/mockResponses.cjs.map +1 -1
  10. package/dist/NotificationServicesController/mocks/mockResponses.d.cts +1 -0
  11. package/dist/NotificationServicesController/mocks/mockResponses.d.cts.map +1 -1
  12. package/dist/NotificationServicesController/mocks/mockResponses.d.mts +1 -0
  13. package/dist/NotificationServicesController/mocks/mockResponses.d.mts.map +1 -1
  14. package/dist/NotificationServicesController/mocks/mockResponses.mjs +8 -1
  15. package/dist/NotificationServicesController/mocks/mockResponses.mjs.map +1 -1
  16. package/dist/NotificationServicesController/services/api-notifications.cjs +43 -4
  17. package/dist/NotificationServicesController/services/api-notifications.cjs.map +1 -1
  18. package/dist/NotificationServicesController/services/api-notifications.d.cts +23 -2
  19. package/dist/NotificationServicesController/services/api-notifications.d.cts.map +1 -1
  20. package/dist/NotificationServicesController/services/api-notifications.d.mts +23 -2
  21. package/dist/NotificationServicesController/services/api-notifications.d.mts.map +1 -1
  22. package/dist/NotificationServicesController/services/api-notifications.mjs +40 -3
  23. package/dist/NotificationServicesController/services/api-notifications.mjs.map +1 -1
  24. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,9 +9,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
+ - **BREAKING:** `getNotificationsApiConfigCached` now returns `null` when the Trigger API could not be read, and an empty array only when the API reported that no address is subscribed ([#9985](https://github.com/MetaMask/core/pull/9985))
13
+ - Previously both cases returned an empty array, so callers could not tell "the user has nothing enabled" from "we failed to ask", and acting on the guess re-subscribed or unregistered addresses behind the user.
12
14
  - Bump `@metamask/keyring-controller` from `^27.1.0` to `^27.1.1` ([#9791](https://github.com/MetaMask/core/pull/9791))
13
15
  - Bump `@metamask/authenticated-user-storage` from `^3.0.1` to `^3.0.2` ([#9972](https://github.com/MetaMask/core/pull/9972))
14
16
 
17
+ ### Fixed
18
+
19
+ - **BREAKING:** Wallet-activity addresses are once again sourced from the keyring and the Trigger API instead of Authenticated User Storage, which stops users receiving notifications for addresses they do not hold ([#9985](https://github.com/MetaMask/core/pull/9985))
20
+ - Authenticated User Storage is keyed by canonical profile ID, which profile pairing shares across every SRP belonging to the same user, so an address list stored there pooled the addresses of unrelated SRPs and delivered each installation the union of all of them. Addresses are keyring-scoped and cannot live in profile-scoped storage.
21
+ - `checkAccountsPresence`, `fetchAndUpdateMetamaskNotifications` and `enablePushNotifications` now take the candidate addresses from the keyring and read the per-address enabled bit from the Trigger API. An installation can therefore only ever ask about addresses it holds.
22
+ - `enableAccounts` and `disableAccounts` now write subscriptions to the Trigger API. The endpoint is a per-address upsert, so two installations authenticating as the same profile no longer clobber each other's subscriptions.
23
+ - `createOnChainTriggers` no longer writes addresses into `walletActivity.accounts`; it writes an empty list. It subscribes the keyring's accounts only when initializing preferences for the first time and the Trigger API has no subscriptions yet, so the daily re-subscribe can no longer re-enable accounts the user turned off.
24
+ - The user-level `walletActivity.inAppNotificationsEnabled` and `walletActivity.pushNotificationsEnabled` toggles are still read from Authenticated User Storage. They contain no addresses, so they remain correct to share across a paired profile. An unreadable preferences blob is treated as both toggles enabled, so a storage outage no longer empties the notification list.
25
+ - Unregister the device from push notifications when no account has notifications enabled ([#9985](https://github.com/MetaMask/core/pull/9985))
26
+ - The push API rejects a registration with no addresses, and that request is what performs the delete-and-reinsert of the device's links, so the rejection left the previous links in place and push kept arriving after a user disabled every account.
27
+ - `enableAccounts` and `disableAccounts` now reject when the Trigger API rejects the subscription change, instead of reporting success and caching a state the server never applied ([#9985](https://github.com/MetaMask/core/pull/9985))
28
+ - A 4xx/5xx response was treated as success, so the settings UI showed the new toggle position and push links were added or removed for a subscription that did not change.
29
+ - A failed Trigger API config read no longer re-enables accounts or unregisters the device ([#9985](https://github.com/MetaMask/core/pull/9985))
30
+ - `createOnChainTriggers` now fails instead of reading a failed config query as "no subscriptions yet", which could subscribe every keyring account for a user who had turned them off, and it writes the preferences blob only after those subscriptions are in place so a failed run can still be retried as a first-time setup.
31
+ - `enablePushNotifications` leaves the device's existing push links alone rather than unregistering it, and `checkAccountsPresence` rejects rather than reporting every account as disabled.
32
+
15
33
  ## [26.0.1]
16
34
 
17
35
  ### Changed
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- var _NotificationServicesController_instances, _NotificationServicesController_keyringController, _NotificationServicesController_auth, _NotificationServicesController_pushNotifications, _NotificationServicesController_accounts, _NotificationServicesController_locale, _NotificationServicesController_featureAnnouncementEnv, _NotificationServicesController_env, _NotificationServicesController_clearLoadingStates, _NotificationServicesController_assertAuthEnabled, _NotificationServicesController_enableAuth, _NotificationServicesController_getBearerToken, _NotificationServicesController_registerWalletActivityAddresses, _NotificationServicesController_setIsUpdatingMetamaskNotifications, _NotificationServicesController_setIsFetchingMetamaskNotifications, _NotificationServicesController_setIsCheckingAccountsPresence, _NotificationServicesController_updateUpdatingAccountsState, _NotificationServicesController_clearUpdatingAccountsState;
16
+ var _NotificationServicesController_instances, _NotificationServicesController_keyringController, _NotificationServicesController_auth, _NotificationServicesController_pushNotifications, _NotificationServicesController_accounts, _NotificationServicesController_locale, _NotificationServicesController_featureAnnouncementEnv, _NotificationServicesController_env, _NotificationServicesController_clearLoadingStates, _NotificationServicesController_assertAuthEnabled, _NotificationServicesController_enableAuth, _NotificationServicesController_getBearerToken, _NotificationServicesController_isWalletActivityPushEnabled, _NotificationServicesController_registerPushNotifications, _NotificationServicesController_setIsUpdatingMetamaskNotifications, _NotificationServicesController_setIsFetchingMetamaskNotifications, _NotificationServicesController_setIsCheckingAccountsPresence, _NotificationServicesController_updateUpdatingAccountsState, _NotificationServicesController_clearUpdatingAccountsState;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.NotificationServicesController = exports.DEFAULT_PRICE_ALERT_PREFERENCES = exports.DEFAULT_AGENTIC_CLI_PREFERENCES = exports.DEFAULT_SOCIAL_AI_PREFERENCES = exports.DEFAULT_PERPS_PREFERENCES = exports.defaultState = exports.ACCOUNTS_UPDATE_DEBOUNCE_TIME_MS = void 0;
19
19
  const authenticated_user_storage_1 = require("@metamask/authenticated-user-storage");
@@ -128,58 +128,48 @@ exports.DEFAULT_SOCIAL_AI_PREFERENCES = {
128
128
  var authenticated_user_storage_2 = require("@metamask/authenticated-user-storage");
129
129
  Object.defineProperty(exports, "DEFAULT_AGENTIC_CLI_PREFERENCES", { enumerable: true, get: function () { return authenticated_user_storage_2.DEFAULT_AGENTIC_CLI_PREFERENCES; } });
130
130
  Object.defineProperty(exports, "DEFAULT_PRICE_ALERT_PREFERENCES", { enumerable: true, get: function () { return authenticated_user_storage_2.DEFAULT_PRICE_ALERT_PREFERENCES; } });
131
+ const filterEnabledAccounts = (triggerConfig) => triggerConfig
132
+ .filter((addressConfig) => Boolean(addressConfig.enabled))
133
+ .map((addressConfig) => addressConfig.address);
131
134
  /**
132
- * Builds wallet-activity preferences from the keyring's current accounts.
135
+ * Returns the subset of `accounts` that has a wallet-activity subscription in
136
+ * the Trigger API, which is the source of truth for the per-address enabled bit.
133
137
  *
134
- * @param accounts - The keyring accounts to build wallet-activity entries for.
135
- * @returns An array of wallet-activity account entries (lower-cased addresses).
136
- */
137
- const buildWalletActivityAccounts = (accounts) => accounts.map(({ address, enabled }) => {
138
- const lowercased = address.toLowerCase();
139
- return {
140
- address: lowercased,
141
- enabled,
142
- };
143
- });
144
- /**
145
- * Builds wallet-activity initialization from the Trigger API. If Trigger has no
146
- * enabled entries for the current keyring accounts, this is a first-time
147
- * notification setup and all current accounts should start enabled.
138
+ * The address universe is always supplied by the caller from the keyring, so the
139
+ * result cannot contain an address this installation does not hold.
148
140
  *
149
- * @param bearerToken - JWT used to query Trigger API.
150
- * @param accounts - The keyring accounts to initialize.
141
+ * @param bearerToken - JWT used to query the Trigger API.
142
+ * @param accounts - The keyring accounts to check.
151
143
  * @param env - The environment to use for the Trigger API call.
152
- * @returns Wallet-activity account initialization entries.
144
+ * @returns The enabled addresses, as returned by the Trigger API, or `null` if
145
+ * the Trigger API could not be read.
153
146
  */
154
- const buildWalletActivityAccountsFromTriggerConfig = async (bearerToken, accounts, env) => {
147
+ const getEnabledAccounts = async (bearerToken, accounts, env) => {
155
148
  const triggerConfig = await (0, api_notifications_js_1.getNotificationsApiConfigCached)(bearerToken, accounts, env);
156
- const triggerConfigByAddress = new Map(triggerConfig.map(({ address, enabled }) => [
157
- address.toLowerCase(),
158
- enabled,
159
- ]));
160
- const hasEnabledTriggerAccount = accounts.some((address) => triggerConfigByAddress.get(address.toLowerCase()) === true);
161
- return accounts.map((address) => ({
162
- address,
163
- enabled: hasEnabledTriggerAccount
164
- ? (triggerConfigByAddress.get(address.toLowerCase()) ?? false)
165
- : true,
166
- }));
149
+ if (triggerConfig === null) {
150
+ return null;
151
+ }
152
+ return filterEnabledAccounts(triggerConfig);
167
153
  };
168
154
  /**
169
155
  * Builds a fresh `NotificationPreferences` blob using hardcoded defaults for
170
- * Perps, Social AI, and Agentic CLI, the supplied wallet-activity accounts and
171
- * the user's marketing/product-announcement flags.
156
+ * Perps, Social AI, and Agentic CLI and the user's marketing/product-announcement
157
+ * flags.
158
+ *
159
+ * `walletActivity.accounts` is deliberately left empty. Addresses are scoped to a
160
+ * keyring, but this blob is keyed by canonical profile ID, which pairing shares
161
+ * across every SRP belonging to the same user — so storing them here pools the
162
+ * addresses of unrelated SRPs. Subscriptions live in the Trigger API instead.
172
163
  *
173
- * @param walletActivityAccounts - The wallet-activity account config to initialize.
174
164
  * @param hasMarketingConsent - Whether marketing push notifications should be enabled.
175
165
  * @param productAnnouncementEnabled - Whether marketing in-app notifications should be enabled.
176
166
  * @returns A complete `NotificationPreferences` object.
177
167
  */
178
- const buildFreshPreferences = (walletActivityAccounts, hasMarketingConsent, productAnnouncementEnabled) => ({
168
+ const buildFreshPreferences = (hasMarketingConsent, productAnnouncementEnabled) => ({
179
169
  walletActivity: {
180
170
  inAppNotificationsEnabled: true,
181
171
  pushNotificationsEnabled: true,
182
- accounts: buildWalletActivityAccounts(walletActivityAccounts),
172
+ accounts: [],
183
173
  },
184
174
  marketing: {
185
175
  inAppNotificationsEnabled: productAnnouncementEnabled,
@@ -481,13 +471,20 @@ class NotificationServicesController extends base_controller_1.BaseController {
481
471
  */
482
472
  async enablePushNotifications() {
483
473
  try {
484
- const preferences = await this.messenger.call('AuthenticatedUserStorageService:getNotificationPreferences');
485
- const enabledAddresses = (preferences?.walletActivity.accounts ?? [])
486
- .filter((account) => account.enabled)
487
- .map((account) => account.address);
488
- if (enabledAddresses.length > 0) {
489
- await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").enablePushNotifications(enabledAddresses);
474
+ if (!(await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_isWalletActivityPushEnabled).call(this))) {
475
+ await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").disablePushNotifications();
476
+ return;
490
477
  }
478
+ const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
479
+ const { accounts } = __classPrivateFieldGet(this, _NotificationServicesController_accounts, "f").listAccounts();
480
+ const enabledAddresses = await getEnabledAccounts(bearerToken, accounts, __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
481
+ if (enabledAddresses === null) {
482
+ // "No addresses" unregisters the device, so an unreadable subscription
483
+ // list must not be treated as an empty one: leave the existing links
484
+ // alone until we can read the real state.
485
+ return;
486
+ }
487
+ await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_registerPushNotifications).call(this, enabledAddresses);
491
488
  }
492
489
  catch {
493
490
  // Do nothing, failing silently.
@@ -502,10 +499,16 @@ class NotificationServicesController extends base_controller_1.BaseController {
502
499
  async checkAccountsPresence(accounts) {
503
500
  try {
504
501
  __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_setIsCheckingAccountsPresence).call(this, true);
505
- const preferences = await this.messenger.call('AuthenticatedUserStorageService:getNotificationPreferences');
506
- const enabledByAddress = new Map((preferences?.walletActivity.accounts ?? []).map((account) => [
507
- account.address.toLowerCase(),
508
- account.enabled,
502
+ const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
503
+ const triggerConfig = await (0, api_notifications_js_1.getNotificationsApiConfigCached)(bearerToken, accounts, __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
504
+ if (triggerConfig === null) {
505
+ // Reporting every account as disabled would misrepresent the user's
506
+ // settings, so surface the failure instead.
507
+ throw new Error('Failed to read wallet-activity subscriptions');
508
+ }
509
+ const enabledByAddress = new Map(triggerConfig.map((addressConfig) => [
510
+ addressConfig.address.toLowerCase(),
511
+ Boolean(addressConfig.enabled),
509
512
  ]));
510
513
  const result = {};
511
514
  for (const address of accounts) {
@@ -564,27 +567,54 @@ class NotificationServicesController extends base_controller_1.BaseController {
564
567
  __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_setIsUpdatingMetamaskNotifications).call(this, true);
565
568
  const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
566
569
  const { accounts } = __classPrivateFieldGet(this, _NotificationServicesController_accounts, "f").listAccounts();
567
- // 1. Read existing AUS notification preferences and initialize only if absent.
570
+ // 1. Read existing AUS notification preferences. A missing blob is
571
+ // `isMissingAUSConfig` and is initialized in step 3. It is not, on its
572
+ // own, first-time subscribe — that also needs an empty Trigger API list
573
+ // (step 2).
568
574
  const preferences = await this.messenger.call('AuthenticatedUserStorageService:getNotificationPreferences');
569
575
  const hasMarketingConsent = Boolean(opts?.hasMarketingConsent);
570
576
  const productAnnouncementEnabled = Boolean(opts?.productAnnouncementEnabled);
571
- let nextPreferences;
572
- if (preferences === null) {
573
- const walletActivityAccounts = await buildWalletActivityAccountsFromTriggerConfig(bearerToken, accounts, __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
574
- nextPreferences = buildFreshPreferences(walletActivityAccounts, hasMarketingConsent, productAnnouncementEnabled);
577
+ const isMissingAUSConfig = preferences === null;
578
+ const isPushEnabled = preferences?.walletActivity.pushNotificationsEnabled ?? true;
579
+ // 2. Subscribe the keyring's accounts only on first-time setup: no AUS
580
+ // blob and the Trigger API returned an empty list.
581
+ //
582
+ // This method also runs on the daily re-subscribe. An existing AUS blob
583
+ // already rules that path out, including when the user has turned every
584
+ // account off — so we do not treat "no enabled addresses" as "never set
585
+ // up".
586
+ //
587
+ // An empty Trigger API list (not a list of disabled addresses) is what
588
+ // distinguishes "never subscribed" from "subscribed and then disabled".
589
+ // A user upgrading from a client that never wrote a preferences blob
590
+ // therefore keeps whichever accounts they had already disabled.
591
+ const notificationsApiPreferences = await (0, api_notifications_js_1.getNotificationsApiConfigCached)(bearerToken, accounts, __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
592
+ if (notificationsApiPreferences === null) {
593
+ // An unreadable subscription list is not an empty one. Subscribing
594
+ // every account here would re-enable ones the user had turned off, and
595
+ // registering push for that guessed list would send activity for them,
596
+ // so fail and let the caller retry.
597
+ throw new Error('Failed to read wallet-activity subscriptions');
598
+ }
599
+ let accountsWithWalletActivityNotificationsEnabled = filterEnabledAccounts(notificationsApiPreferences);
600
+ const isFirstTimeSetup = isMissingAUSConfig && notificationsApiPreferences.length === 0;
601
+ if (isFirstTimeSetup) {
602
+ await (0, api_notifications_js_1.updateOnChainNotifications)(bearerToken, accounts.map((address) => ({ address, enabled: true })), __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
603
+ // Match the lower-case form the Trigger API echoes back, which is what
604
+ // every other path feeding the push API uses.
605
+ accountsWithWalletActivityNotificationsEnabled = accounts.map((address) => address.toLowerCase());
575
606
  }
576
- if (nextPreferences) {
577
- await this.messenger.call('AuthenticatedUserStorageService:putNotificationPreferences', nextPreferences, __classPrivateFieldGet(this, _NotificationServicesController_featureAnnouncementEnv, "f").platform);
607
+ // 3. Initialize the preferences blob, only once the subscriptions above
608
+ // are in place. Its existence is what makes the next run a re-subscribe
609
+ // rather than a first-time setup, so writing it earlier would let a
610
+ // failed subscribe leave the user enabled with no accounts subscribed
611
+ // and no second chance to seed them.
612
+ if (isMissingAUSConfig) {
613
+ await this.messenger.call('AuthenticatedUserStorageService:putNotificationPreferences', buildFreshPreferences(hasMarketingConsent, productAnnouncementEnabled), __classPrivateFieldGet(this, _NotificationServicesController_featureAnnouncementEnv, "f").platform);
578
614
  }
579
- const effectivePreferences = nextPreferences ?? preferences;
580
- const accountsWithNotifications = (effectivePreferences?.walletActivity.accounts ?? [])
581
- .filter((account) => account.enabled)
582
- .map((account) => account.address);
583
615
  if (opts.registerPushNotifications ?? true) {
584
616
  // Attempt FCM/device registration only; clients must request OS permission separately.
585
- __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f")
586
- .enablePushNotifications(accountsWithNotifications)
587
- .catch(() => {
617
+ __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_registerPushNotifications).call(this, isPushEnabled ? accountsWithWalletActivityNotificationsEnabled : []).catch(() => {
588
618
  // Do Nothing
589
619
  });
590
620
  }
@@ -677,9 +707,8 @@ class NotificationServicesController extends base_controller_1.BaseController {
677
707
  async disableAccounts(accounts) {
678
708
  try {
679
709
  __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_updateUpdatingAccountsState).call(this, accounts);
680
- // Sign-in gate.
681
- await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
682
- await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_registerWalletActivityAddresses).call(this, accounts.map((address) => ({ address, enabled: false })));
710
+ const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
711
+ await (0, api_notifications_js_1.updateOnChainNotifications)(bearerToken, accounts.map((address) => ({ address, enabled: false })), __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
683
712
  await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").deletePushNotificationLinks(accounts);
684
713
  }
685
714
  catch {
@@ -707,9 +736,8 @@ class NotificationServicesController extends base_controller_1.BaseController {
707
736
  async enableAccounts(accounts) {
708
737
  try {
709
738
  __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_updateUpdatingAccountsState).call(this, accounts);
710
- // Sign-in gate.
711
- await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
712
- await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_registerWalletActivityAddresses).call(this, accounts.map((address) => ({ address, enabled: true })));
739
+ const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
740
+ await (0, api_notifications_js_1.updateOnChainNotifications)(bearerToken, accounts.map((address) => ({ address, enabled: true })), __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
713
741
  await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").addPushNotificationLinks(accounts);
714
742
  }
715
743
  catch (error) {
@@ -748,14 +776,20 @@ class NotificationServicesController extends base_controller_1.BaseController {
748
776
  : [];
749
777
  // Raw On Chain Notifications
750
778
  const rawOnChainNotifications = [];
751
- if (isGlobalNotifsEnabled) {
779
+ const isWalletActivityInAppEnabled = notificationPreferences?.walletActivity.inAppNotificationsEnabled ??
780
+ true;
781
+ if (isGlobalNotifsEnabled && isWalletActivityInAppEnabled) {
752
782
  try {
753
783
  const { bearerToken } = await __classPrivateFieldGet(this, _NotificationServicesController_instances, "m", _NotificationServicesController_getBearerToken).call(this);
754
- const addressesWithNotifications = (notificationPreferences?.walletActivity.accounts ?? [])
755
- .filter((account) => account.enabled)
756
- .map((account) => account.address);
757
- const notifications = await (0, api_notifications_js_1.getAPINotifications)(bearerToken, addressesWithNotifications, __classPrivateFieldGet(this, _NotificationServicesController_locale, "f").call(this), __classPrivateFieldGet(this, _NotificationServicesController_featureAnnouncementEnv, "f").platform, __classPrivateFieldGet(this, _NotificationServicesController_env, "f")).catch(() => []);
758
- rawOnChainNotifications.push(...notifications);
784
+ // Addresses come from the keyring, so this installation can only ever
785
+ // ask for activity on accounts it actually holds. The Trigger API
786
+ // narrows that to the ones the user has enabled.
787
+ const { accounts } = __classPrivateFieldGet(this, _NotificationServicesController_accounts, "f").listAccounts();
788
+ const addressesWithNotifications = await getEnabledAccounts(bearerToken, accounts, __classPrivateFieldGet(this, _NotificationServicesController_env, "f"));
789
+ if (addressesWithNotifications !== null) {
790
+ const notifications = await (0, api_notifications_js_1.getAPINotifications)(bearerToken, addressesWithNotifications, __classPrivateFieldGet(this, _NotificationServicesController_locale, "f").call(this), __classPrivateFieldGet(this, _NotificationServicesController_featureAnnouncementEnv, "f").platform, __classPrivateFieldGet(this, _NotificationServicesController_env, "f")).catch(() => []);
791
+ rawOnChainNotifications.push(...notifications);
792
+ }
759
793
  }
760
794
  catch {
761
795
  // Do nothing
@@ -979,53 +1013,44 @@ _NotificationServicesController_keyringController = new WeakMap(), _Notification
979
1013
  throw new Error('Missing BearerToken');
980
1014
  }
981
1015
  return { bearerToken };
982
- }, _NotificationServicesController_registerWalletActivityAddresses =
1016
+ }, _NotificationServicesController_isWalletActivityPushEnabled =
983
1017
  /**
984
- * Updates the `walletActivity.accounts` entries in the user's
985
- * notification-preferences blob in {@link AuthenticatedUserStorageService}.
1018
+ * Reads the global wallet-activity push toggle from
1019
+ * {@link AuthenticatedUserStorageService}.
1020
+ *
1021
+ * Only the channel boolean is read. It is a user-level setting that contains
1022
+ * no addresses, so sharing it across a paired profile is correct. The address
1023
+ * list in the same blob is deliberately ignored — see
1024
+ * {@link buildFreshPreferences}.
986
1025
  *
987
- * `putNotificationPreferences` replaces the entire blob, so we read the
988
- * current preferences, merge the supplied updates into
989
- * `walletActivity.accounts`, and write the result back. This helper is only
990
- * meant to be used for incremental updates (enable/disable individual
991
- * accounts) after the preferences blob has already been initialized via
992
- * {@link createOnChainTriggers}; callers should not rely on it to perform
993
- * first-time initialization.
1026
+ * A missing or unreadable blob counts as enabled, matching the API's "every
1027
+ * toggle is on unless stated otherwise" default, so that a storage outage does
1028
+ * not silently stop notifications.
994
1029
  *
995
- * @param updates - Addresses to register, each with the desired `enabled` flag
1030
+ * @returns Whether wallet-activity push notifications are enabled.
996
1031
  */
997
- async function _NotificationServicesController_registerWalletActivityAddresses(updates) {
998
- if (updates.length === 0) {
999
- return;
1000
- }
1001
- const currentPreferences = await this.messenger
1032
+ async function _NotificationServicesController_isWalletActivityPushEnabled() {
1033
+ const preferences = await this.messenger
1002
1034
  .call('AuthenticatedUserStorageService:getNotificationPreferences')
1003
- .catch((error) => {
1004
- loglevel_1.default.error('Failed to get notification preferences. Re-initializing them instead.', error);
1005
- // TODO: return null once validation error captured
1006
- // return null;
1007
- throw error;
1008
- });
1009
- if (!currentPreferences) {
1010
- loglevel_1.default.warn('Preferences blob not yet initialized; run `createOnChainTriggers` first.');
1035
+ .catch(() => null);
1036
+ return preferences?.walletActivity.pushNotificationsEnabled ?? true;
1037
+ }, _NotificationServicesController_registerPushNotifications =
1038
+ /**
1039
+ * Registers this device for push notifications on the given addresses.
1040
+ *
1041
+ * An empty list cannot be sent: the push API rejects a registration with no
1042
+ * addresses, and because that request is what performs the delete-and-reinsert
1043
+ * of the device's links, a rejection leaves the previous links in place and
1044
+ * push keeps arriving. So "no addresses" has to mean unregistering the device.
1045
+ *
1046
+ * @param addresses - The addresses to receive push notifications for.
1047
+ */
1048
+ async function _NotificationServicesController_registerPushNotifications(addresses) {
1049
+ if (addresses.length === 0) {
1050
+ await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").disablePushNotifications();
1011
1051
  return;
1012
1052
  }
1013
- const accountsByAddress = new Map(currentPreferences.walletActivity.accounts.map((account) => [
1014
- account.address.toLowerCase(),
1015
- { ...account, address: account.address.toLowerCase() },
1016
- ]));
1017
- for (const update of updates) {
1018
- const address = update.address.toLowerCase();
1019
- accountsByAddress.set(address, { address, enabled: update.enabled });
1020
- }
1021
- const nextPreferences = {
1022
- ...currentPreferences,
1023
- walletActivity: {
1024
- ...currentPreferences.walletActivity,
1025
- accounts: [...accountsByAddress.values()],
1026
- },
1027
- };
1028
- await this.messenger.call('AuthenticatedUserStorageService:putNotificationPreferences', nextPreferences, __classPrivateFieldGet(this, _NotificationServicesController_featureAnnouncementEnv, "f").platform);
1053
+ await __classPrivateFieldGet(this, _NotificationServicesController_pushNotifications, "f").enablePushNotifications(addresses);
1029
1054
  }, _NotificationServicesController_setIsUpdatingMetamaskNotifications = function _NotificationServicesController_setIsUpdatingMetamaskNotifications(isUpdatingMetamaskNotifications) {
1030
1055
  this.update((state) => {
1031
1056
  state.isUpdatingMetamaskNotifications = isUpdatingMetamaskNotifications;