diff --git a/packages/notification-services-controller/CHANGELOG.md b/packages/notification-services-controller/CHANGELOG.md index bf5750cbca9..d9da7fa4c58 100644 --- a/packages/notification-services-controller/CHANGELOG.md +++ b/packages/notification-services-controller/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Debounce `KeyringController:stateChange` handler to reduce redundant notification subscription calls during rapid account syncing ([#7980](https://github.com/MetaMask/core/pull/7980)) - Filter out Product Account announcements notifications older than 3 months ([#7884](https://github.com/MetaMask/core/pull/7884)) - Bump `@metamask/controller-utils` from `^11.18.0` to `^11.19.0` ([#7995](https://github.com/MetaMask/core/pull/7995)) +- Register notification accounts from all keyrings instead of only the first HD keyring, so notification setup now includes addresses from HD, hardware, imported, and snap keyrings ([#8108](https://github.com/MetaMask/core/pull/8108)) +- Add push token unlink support for account removal by deleting `/api/v2/token` links for `{ address, platform }` pairs when notification accounts are disabled (for example during SRP removal) ([#8108](https://github.com/MetaMask/core/pull/8108)) ## [22.0.0] diff --git a/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.test.ts b/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.test.ts index c09a7f953af..b2789161ee3 100644 --- a/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.test.ts +++ b/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.test.ts @@ -47,6 +47,7 @@ import { notificationsConfigCache } from './services/notification-config-cache'; import type { INotification, OrderInput } from './types'; import type { NotificationServicesPushControllerDisablePushNotificationsAction, + NotificationServicesPushControllerDeletePushNotificationLinksAction, NotificationServicesPushControllerEnablePushNotificationsAction, NotificationServicesPushControllerSubscribeToNotificationsAction, } from '../NotificationServicesPushController'; @@ -498,6 +499,158 @@ describe('NotificationServicesController', () => { expect(mockEnablePushNotifications).toHaveBeenCalled(); }); + it('tracks accounts from all keyrings when creating triggers', async () => { + const { + messenger, + mockGetConfig, + mockUpdateNotifications, + mockKeyringControllerGetState, + } = arrangeMocks({ + // Mock no existing notifications + mockGetConfig: () => + mockGetOnChainNotificationsConfig({ + status: 200, + body: [], + }), + }); + + mockKeyringControllerGetState.mockReturnValue({ + isUnlocked: true, + keyrings: [ + { + accounts: [ADDRESS_1], + type: KeyringTypes.hd, + metadata: { + id: 'srp-1', + name: 'SRP 1', + }, + }, + { + accounts: [ADDRESS_2], + type: KeyringTypes.hd, + metadata: { + id: 'srp-2', + name: 'SRP 2', + }, + }, + ], + }); + + const controller = new NotificationServicesController({ + messenger, + env: { featureAnnouncements: featureAnnouncementsEnv }, + }); + + await controller.createOnChainTriggers(); + + expect(mockGetConfig.isDone()).toBe(true); + expect(mockUpdateNotifications.isDone()).toBe(true); + expect(controller.state.subscriptionAccountsSeen).toStrictEqual([ + ADDRESS_1, + ADDRESS_2, + ]); + }); + + it('deduplicates and filters non-Ethereum accounts when creating triggers', async () => { + const { + messenger, + mockGetConfig, + mockUpdateNotifications, + mockKeyringControllerGetState, + } = arrangeMocks({ + // Mock no existing notifications + mockGetConfig: () => + mockGetOnChainNotificationsConfig({ + status: 200, + body: [], + }), + }); + + mockKeyringControllerGetState.mockReturnValue({ + isUnlocked: true, + keyrings: [ + { + accounts: [ADDRESS_1, ADDRESS_1.toLowerCase(), 'NotAnAddress'], + type: KeyringTypes.hd, + metadata: { + id: 'srp-1', + name: 'SRP 1', + }, + }, + { + accounts: [ + ADDRESS_2, + '7xKXtg2CW6y7J2wMmkf8VbM8dYb6u3H3V8bLxT64d4oR', + ], + type: KeyringTypes.hd, + metadata: { + id: 'srp-2', + name: 'SRP 2', + }, + }, + ], + }); + + const controller = new NotificationServicesController({ + messenger, + env: { featureAnnouncements: featureAnnouncementsEnv }, + }); + + await controller.createOnChainTriggers(); + + expect(mockGetConfig.isDone()).toBe(true); + expect(mockUpdateNotifications.isDone()).toBe(true); + expect(controller.state.subscriptionAccountsSeen).toStrictEqual([ + ADDRESS_1, + ADDRESS_2, + ]); + }); + + it('normalizes non-checksummed mixed-case addresses before filtering', async () => { + const { + messenger, + mockGetConfig, + mockUpdateNotifications, + mockKeyringControllerGetState, + } = arrangeMocks({ + mockGetConfig: () => + mockGetOnChainNotificationsConfig({ + status: 200, + body: [], + }), + }); + + const nonChecksummedMixedCaseAddress = + '0xd8Da6bf26964af9d7eeD9e03E53415D37aa96045'; + + mockKeyringControllerGetState.mockReturnValue({ + isUnlocked: true, + keyrings: [ + { + accounts: [nonChecksummedMixedCaseAddress], + type: KeyringTypes.hd, + metadata: { + id: 'srp-1', + name: 'SRP 1', + }, + }, + ], + }); + + const controller = new NotificationServicesController({ + messenger, + env: { featureAnnouncements: featureAnnouncementsEnv }, + }); + + await controller.createOnChainTriggers(); + + expect(mockGetConfig.isDone()).toBe(true); + expect(mockUpdateNotifications.isDone()).toBe(true); + expect(controller.state.subscriptionAccountsSeen).toStrictEqual([ + ADDRESS_1, + ]); + }); + it('does not register notifications when notifications already exist and not resetting (however does update push registrations)', async () => { const { messenger, @@ -618,7 +771,11 @@ describe('NotificationServicesController', () => { }; it('disables notifications for given accounts', async () => { - const { messenger, mockUpdateNotifications } = arrangeMocks(); + const { + messenger, + mockUpdateNotifications, + mockDeletePushNotificationLinks, + } = arrangeMocks(); const controller = new NotificationServicesController({ messenger, env: { featureAnnouncements: featureAnnouncementsEnv }, @@ -627,6 +784,7 @@ describe('NotificationServicesController', () => { await controller.disableAccounts([ADDRESS_1]); expect(mockUpdateNotifications.isDone()).toBe(true); + expect(mockDeletePushNotificationLinks).toHaveBeenCalledWith([ADDRESS_1]); }); it('throws errors when invalid auth', async () => { @@ -1554,6 +1712,7 @@ function mockNotificationMessenger(): { mockIsSignedIn: jest.Mock; mockAuthPerformSignIn: jest.Mock; mockDisablePushNotifications: jest.Mock; + mockDeletePushNotificationLinks: jest.Mock; mockEnablePushNotifications: jest.Mock; mockSubscribeToPushNotifications: jest.Mock; mockKeyringControllerGetState: jest.Mock; @@ -1578,6 +1737,7 @@ function mockNotificationMessenger(): { 'AuthenticationController:isSignedIn', 'AuthenticationController:performSignIn', 'NotificationServicesPushController:disablePushNotifications', + 'NotificationServicesPushController:deletePushNotificationLinks', 'NotificationServicesPushController:enablePushNotifications', 'NotificationServicesPushController:subscribeToPushNotifications', ], @@ -1608,6 +1768,11 @@ function mockNotificationMessenger(): { const mockDisablePushNotifications = typedMockAction(); + const mockDeletePushNotificationLinks = + typedMockAction().mockResolvedValue( + true, + ); + const mockEnablePushNotifications = typedMockAction(); @@ -1656,6 +1821,13 @@ function mockNotificationMessenger(): { return mockDisablePushNotifications(); } + if ( + actionType === + 'NotificationServicesPushController:deletePushNotificationLinks' + ) { + return mockDeletePushNotificationLinks(params[0]); + } + if ( actionType === 'NotificationServicesPushController:enablePushNotifications' @@ -1682,6 +1854,7 @@ function mockNotificationMessenger(): { mockIsSignedIn, mockAuthPerformSignIn, mockDisablePushNotifications, + mockDeletePushNotificationLinks, mockEnablePushNotifications, mockSubscribeToPushNotifications, mockKeyringControllerGetState, diff --git a/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.ts b/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.ts index 957ff38a450..36351586f01 100644 --- a/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.ts +++ b/packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.ts @@ -8,7 +8,6 @@ import { isValidHexAddress, toChecksumHexAddress, } from '@metamask/controller-utils'; -import { KeyringTypes } from '@metamask/keyring-controller'; import type { KeyringControllerStateChangeEvent, KeyringControllerGetStateAction, @@ -45,6 +44,7 @@ import type { OrderInput } from './types/perps'; import type { NotificationServicesPushControllerEnablePushNotificationsAction, NotificationServicesPushControllerDisablePushNotificationsAction, + NotificationServicesPushControllerDeletePushNotificationLinksAction, NotificationServicesPushControllerSubscribeToNotificationsAction, NotificationServicesPushControllerStateChangeEvent, NotificationServicesPushControllerOnNewNotificationEvent, @@ -234,6 +234,7 @@ type AllowedActions = // Push Notifications Controller Requests | NotificationServicesPushControllerEnablePushNotificationsAction | NotificationServicesPushControllerDisablePushNotificationsAction + | NotificationServicesPushControllerDeletePushNotificationLinksAction | NotificationServicesPushControllerSubscribeToNotificationsAction; // Events @@ -358,6 +359,16 @@ export default class NotificationServicesController extends BaseController< // Do nothing, failing silently. } }, + deletePushNotificationLinks: async (addresses: string[]): Promise => { + try { + await this.messenger.call( + 'NotificationServicesPushController:deletePushNotificationLinks', + addresses, + ); + } catch { + // Do nothing, failing silently. + } + }, subscribe: (): void => { this.messenger.subscribe( 'NotificationServicesPushController:onNewNotifications', @@ -399,11 +410,24 @@ export default class NotificationServicesController extends BaseController< getNotificationAccounts: (): string[] | null => { const { keyrings } = this.messenger.call('KeyringController:getState'); - const firstHDKeyring = keyrings.find( - (keyring) => keyring.type === KeyringTypes.hd.toString(), - ); - const keyringAccounts = firstHDKeyring?.accounts ?? null; - return keyringAccounts; + const keyringAccounts = [ + ...new Set( + keyrings + .flatMap((keyring) => keyring.accounts) + .map((address) => { + try { + return toChecksumHexAddress(address); + } catch { + return null; + } + }) + .filter( + (address): address is string => + address !== null && isValidHexAddress(address), + ), + ), + ]; + return keyringAccounts.length > 0 ? keyringAccounts : null; }, /** @@ -950,6 +974,8 @@ export default class NotificationServicesController extends BaseController< accounts.map((address) => ({ address, enabled: false })), this.#env, ); + + await this.#pushNotifications.deletePushNotificationLinks(accounts); } catch { throw new Error('Failed to delete OnChain triggers'); } finally { diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.test.ts b/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.test.ts index f0d726f2630..bbe34d5c886 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.test.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.test.ts @@ -25,6 +25,7 @@ describe('NotificationServicesPushController', () => { ): { activatePushNotificationsMock: jest.SpyInstance; deactivatePushNotificationsMock: jest.SpyInstance; + deleteLinksAPIMock: jest.SpyInstance; } => { const activatePushNotificationsMock = jest .spyOn(services, 'activatePushNotifications') @@ -34,9 +35,14 @@ describe('NotificationServicesPushController', () => { .spyOn(services, 'deactivatePushNotifications') .mockResolvedValue(true); + const deleteLinksAPIMock = jest + .spyOn(services, 'deleteLinksAPI') + .mockResolvedValue(true); + return { activatePushNotificationsMock, deactivatePushNotificationsMock, + deleteLinksAPIMock, }; }; @@ -280,6 +286,67 @@ describe('NotificationServicesPushController', () => { }); }); + describe('deletePushNotificationLinks', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should call deleteLinksAPI with addresses and platform', async () => { + const mocks = arrangeServicesMocks(); + const { controller, messenger } = arrangeMockMessenger({ + state: { + fcmToken: MOCK_FCM_TOKEN, + isPushEnabled: true, + isUpdatingFCMToken: false, + }, + }); + mockAuthBearerTokenCall(messenger); + + const result = + await controller.deletePushNotificationLinks(MOCK_ADDRESSES); + + expect(mocks.deleteLinksAPIMock).toHaveBeenCalledWith({ + bearerToken: MOCK_JWT, + addresses: MOCK_ADDRESSES, + platform: 'extension', + token: MOCK_FCM_TOKEN, + env: 'prd', + }); + expect(result).toBe(true); + }); + + it('should return false when push feature is disabled', async () => { + const mocks = arrangeServicesMocks(); + const { controller } = arrangeMockMessenger({ + isPushFeatureEnabled: false, + }); + + const result = + await controller.deletePushNotificationLinks(MOCK_ADDRESSES); + + expect(mocks.deleteLinksAPIMock).not.toHaveBeenCalled(); + expect(result).toBe(false); + }); + + it('should return false when there is no token to delete', async () => { + const mocks = arrangeServicesMocks(); + const { controller, messenger } = arrangeMockMessenger({ + state: { + fcmToken: '', + isPushEnabled: true, + isUpdatingFCMToken: false, + }, + }); + mockAuthBearerTokenCall(messenger); + + const result = + await controller.deletePushNotificationLinks(MOCK_ADDRESSES); + + expect(mocks.deleteLinksAPIMock).not.toHaveBeenCalled(); + expect(result).toBe(false); + }); + }); + describe('metadata', () => { it('includes expected state in debug snapshots', () => { const { controller } = arrangeMockMessenger(); diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.ts b/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.ts index 164a46283ac..7d2546c2ed4 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/NotificationServicesPushController.ts @@ -11,6 +11,7 @@ import log from 'loglevel'; import type { ENV } from './services/endpoints'; import { activatePushNotifications, + deleteLinksAPI, deactivatePushNotifications, } from './services/services'; import type { PushNotificationEnv } from './types'; @@ -48,13 +49,19 @@ export type NotificationServicesPushControllerSubscribeToNotificationsAction = { type: `${typeof controllerName}:subscribeToPushNotifications`; handler: NotificationServicesPushController['subscribeToPushNotifications']; }; +export type NotificationServicesPushControllerDeletePushNotificationLinksAction = + { + type: `${typeof controllerName}:deletePushNotificationLinks`; + handler: NotificationServicesPushController['deletePushNotificationLinks']; + }; export type Actions = | NotificationServicesPushControllerGetStateAction | NotificationServicesPushControllerEnablePushNotificationsAction | NotificationServicesPushControllerDisablePushNotificationsAction | NotificationServicesPushControllerUpdateTriggerPushNotificationsAction - | NotificationServicesPushControllerSubscribeToNotificationsAction; + | NotificationServicesPushControllerSubscribeToNotificationsAction + | NotificationServicesPushControllerDeletePushNotificationLinksAction; type AllowedActions = AuthenticationController.AuthenticationControllerGetBearerTokenAction; @@ -217,6 +224,10 @@ export default class NotificationServicesPushController extends BaseController< 'NotificationServicesPushController:subscribeToPushNotifications', this.subscribeToPushNotifications.bind(this), ); + this.messenger.registerActionHandler( + 'NotificationServicesPushController:deletePushNotificationLinks', + this.deletePushNotificationLinks.bind(this), + ); } #clearLoadingStates(): void { @@ -383,6 +394,39 @@ export default class NotificationServicesPushController extends BaseController< this.#updatePushState({ type: 'disable' }); } + /** + * Deletes backend push notification links for the given addresses on the current platform. + * This is used when accounts are removed (for example SRP removal), so backend can remove + * all associated FCM tokens for those address/platform pairs. + * + * @param addresses - Addresses that should be unlinked from push notifications. + * @returns Whether the delete request succeeded. + */ + public async deletePushNotificationLinks( + addresses: string[], + ): Promise { + if ( + !this.#config.isPushFeatureEnabled || + addresses.length === 0 || + !this.state.fcmToken + ) { + return false; + } + + try { + const bearerToken = await this.#getAndAssertBearerToken(); + return await deleteLinksAPI({ + bearerToken, + addresses, + platform: this.#config.platform, + token: this.state.fcmToken, + env: this.#config.env ?? 'prd', + }); + } catch { + return false; + } + } + /** * Updates the triggers for push notifications. * This method is responsible for updating the server with the new set of addresses that should trigger push notifications. diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/__fixtures__/mockServices.ts b/packages/notification-services-controller/src/NotificationServicesPushController/__fixtures__/mockServices.ts index 75129d47d51..d4610a919ec 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/__fixtures__/mockServices.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/__fixtures__/mockServices.ts @@ -1,6 +1,9 @@ import nock from 'nock'; -import { getMockUpdatePushNotificationLinksResponse } from '../mocks/mockResponse'; +import { + getMockDeletePushNotificationLinksResponse, + getMockUpdatePushNotificationLinksResponse, +} from '../mocks/mockResponse'; type MockReply = { status: nock.StatusCode; @@ -20,3 +23,20 @@ export const mockEndpointUpdatePushNotificationLinks = ( return mockEndpoint; }; + +export const mockEndpointDeletePushNotificationLinks = ( + mockReply?: MockReply, + requestBody?: nock.RequestBodyMatcher, +): nock.Scope => { + const mockResponse = getMockDeletePushNotificationLinksResponse(); + const reply = mockReply ?? { + status: 204, + body: mockResponse.response, + }; + + const mockEndpoint = nock(mockResponse.url) + .delete('', requestBody) + .reply(reply.status); + + return mockEndpoint; +}; diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/mocks/mockResponse.ts b/packages/notification-services-controller/src/NotificationServicesPushController/mocks/mockResponse.ts index 9b83fca11b4..f180c48522a 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/mocks/mockResponse.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/mocks/mockResponse.ts @@ -2,7 +2,7 @@ import { REGISTRATION_TOKENS_ENDPOINT } from '../services/endpoints'; type MockResponse = { url: string | RegExp; - requestMethod: 'GET' | 'POST' | 'PUT'; + requestMethod: 'GET' | 'POST' | 'PUT' | 'DELETE'; response: unknown; }; @@ -16,6 +16,14 @@ export const getMockUpdatePushNotificationLinksResponse = (): MockResponse => { } satisfies MockResponse; }; +export const getMockDeletePushNotificationLinksResponse = (): MockResponse => { + return { + url: REGISTRATION_TOKENS_ENDPOINT(), + requestMethod: 'DELETE', + response: null, + } satisfies MockResponse; +}; + export const MOCK_FCM_RESPONSE = { name: '', token: 'fcm-token', diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/services/services.test.ts b/packages/notification-services-controller/src/NotificationServicesPushController/services/services.test.ts index 69c3de3a2b5..97e5103b9f6 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/services/services.test.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/services/services.test.ts @@ -3,9 +3,13 @@ import log from 'loglevel'; import { activatePushNotifications, deactivatePushNotifications, + deleteLinksAPI, updateLinksAPI, } from './services'; -import { mockEndpointUpdatePushNotificationLinks } from '../__fixtures__/mockServices'; +import { + mockEndpointDeletePushNotificationLinks, + mockEndpointUpdatePushNotificationLinks, +} from '../__fixtures__/mockServices'; import type { PushNotificationEnv } from '../types/firebase'; // Testing util to clean up verbose logs when testing errors @@ -125,6 +129,53 @@ describe('NotificationServicesPushController Services', () => { }); }); + describe('deleteLinksAPI', () => { + const act = async (): Promise => + await deleteLinksAPI({ + bearerToken: MOCK_JWT, + addresses: MOCK_ADDRESSES, + platform: 'extension', + token: MOCK_REG_TOKEN, + }); + + it('should return true if links are successfully deleted', async () => { + const mockAPI = mockEndpointDeletePushNotificationLinks(undefined, { + addresses: MOCK_ADDRESSES, + registration_token: { + platform: 'extension', + token: MOCK_REG_TOKEN, + }, + }); + const result = await act(); + expect(mockAPI.isDone()).toBe(true); + expect(result).toBe(true); + }); + + it('should return false if the links API delete fails', async () => { + const mockAPI = mockEndpointDeletePushNotificationLinks( + { status: 500 }, + { + addresses: MOCK_ADDRESSES, + registration_token: { + platform: 'extension', + token: MOCK_REG_TOKEN, + }, + }, + ); + const result = await act(); + expect(mockAPI.isDone()).toBe(true); + expect(result).toBe(false); + }); + + it('should return false if an error is thrown', async () => { + jest + .spyOn(global, 'fetch') + .mockRejectedValue(new Error('MOCK FAIL FETCH')); + const result = await act(); + expect(result).toBe(false); + }); + }); + describe('deactivatePushNotifications', () => { // Internal testing utility - return type is inferred // eslint-disable-next-line @typescript-eslint/explicit-function-return-type diff --git a/packages/notification-services-controller/src/NotificationServicesPushController/services/services.ts b/packages/notification-services-controller/src/NotificationServicesPushController/services/services.ts index 45f33a37559..26841844a21 100644 --- a/packages/notification-services-controller/src/NotificationServicesPushController/services/services.ts +++ b/packages/notification-services-controller/src/NotificationServicesPushController/services/services.ts @@ -13,6 +13,8 @@ export type RegToken = { oldToken?: string; }; +export type RegistrationPlatform = 'extension' | 'mobile'; + /** * Links API Response Shape */ @@ -28,6 +30,16 @@ export type PushTokenRequest = { }; }; +export type DeletePushTokenRequest = { + addresses: string[]; + // API request uses snake_case for this property + // eslint-disable-next-line @typescript-eslint/naming-convention + registration_token: { + platform: RegistrationPlatform; + token: string; + }; +}; + type UpdatePushTokenParams = { bearerToken: string; addresses: string[]; @@ -66,6 +78,48 @@ export async function updateLinksAPI( } } +type DeletePushTokenParams = { + bearerToken: string; + addresses: string[]; + platform: RegistrationPlatform; + token: string; + env?: ENV; +}; + +/** + * Deletes push notification links for addresses and platform. + * + * @param params - params for deleting registration links + * @returns A promise that resolves with true if the delete request was successful, false otherwise. + */ +export async function deleteLinksAPI( + params: DeletePushTokenParams, +): Promise { + try { + const body: DeletePushTokenRequest = { + addresses: params.addresses, + registration_token: { + platform: params.platform, + token: params.token, + }, + }; + const response = await fetch( + endpoints.REGISTRATION_TOKENS_ENDPOINT(params.env), + { + method: 'DELETE', + headers: { + Authorization: `Bearer ${params.bearerToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }, + ); + return response.ok; + } catch { + return false; + } +} + type ActivatePushNotificationsParams = { // Create Push Token env: PushNotificationEnv;