From 4cfc2d8c49ab7331782f3d38ed31056df0436f4f Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 30 Oct 2025 20:59:55 +0100 Subject: [PATCH 1/8] feat: add new dapp handlers --- .../connections/dAppConnection/registry.ts | 6 ++ .../middlewares/PermissionMiddleware.ts | 3 + .../handlers/avalanche_getSettings.ts | 37 ++++++++++++ .../handlers/avalanche_setCurrency.ts | 60 +++++++++++++++++++ .../handlers/avalanche_setLanguage.ts | 60 +++++++++++++++++++ packages/types/src/dapp-connection.ts | 3 + 6 files changed, 169 insertions(+) create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts diff --git a/packages/service-worker/src/connections/dAppConnection/registry.ts b/packages/service-worker/src/connections/dAppConnection/registry.ts index eb71e3560..5276c602e 100644 --- a/packages/service-worker/src/connections/dAppConnection/registry.ts +++ b/packages/service-worker/src/connections/dAppConnection/registry.ts @@ -39,6 +39,9 @@ import { AccountsChangedCAEvents } from '../../services/accounts/events/accounts import { RequestAccountPermissionHandler } from '../../services/web3/handlers/wallet_requestAccountPermission'; import { WalletGetNetworkStateHandler } from '~/services/network/handlers/wallet_getNetworkState'; import { NetworkStateChangedEvents } from '~/services/network/events/networkStateChanged'; +import { AvalancheSetLanguageHandler } from '~/services/settings/handlers/avalanche_setLanguage'; +import { AvalancheGetSettingsHandler } from '~/services/settings/handlers/avalanche_getSettings'; +import { AvalancheSetCurrencyHandler } from '~/services/settings/handlers/avalanche_setCurrency'; /** * TODO: GENERATE THIS FILE AS PART OF THE BUILD PROCESS @@ -88,6 +91,9 @@ const SHARED_HANDLERS = [ token: 'DAppRequestHandler', useToken: WalletGetNetworkStateHandler, }, + { token: 'DAppRequestHandler', useToken: AvalancheSetLanguageHandler }, + { token: 'DAppRequestHandler', useToken: AvalancheGetSettingsHandler }, + { token: 'DAppRequestHandler', useToken: AvalancheSetCurrencyHandler }, ]; const LEGACY_REQUEST_HANDLERS = [ diff --git a/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts b/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts index 18ceeb401..4f2861228 100644 --- a/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts +++ b/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts @@ -119,6 +119,9 @@ const CORE_METHODS = Object.freeze([ DAppProviderRequest.BITCOIN_SEND_TRANSACTION, DAppProviderRequest.WALLET_RENAME, DAppProviderRequest.WALLET_GET_NETWORK_STATE, + DAppProviderRequest.AVALANCHE_SET_LANGUAGE, + DAppProviderRequest.AVALANCHE_GET_SETTINGS, + DAppProviderRequest.AVALANCHE_SET_CURRENCY, ]); export function PermissionMiddleware( diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts new file mode 100644 index 000000000..3ab4e80b0 --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts @@ -0,0 +1,37 @@ +import { DAppRequestHandler, DAppProviderRequest } from '@core/types'; +import { injectable } from 'tsyringe'; +import { SettingsService } from '../SettingsService'; + +// Avalanche add account handler +// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts +@injectable() +export class AvalancheGetSettingsHandler extends DAppRequestHandler { + methods = [DAppProviderRequest.AVALANCHE_GET_SETTINGS]; + + constructor(private settingsService: SettingsService) { + super(); + } + + handleAuthenticated = async ({ request }) => { + try { + const settings = await this.settingsService.getSettings(); + console.log('settings: ', settings); + return { + ...request, + result: settings, + }; + } catch (e: any) { + return { + ...request, + error: e.toString(), + }; + } + }; + + handleUnauthenticated = ({ request }) => { + return { + ...request, + error: 'account not connected', + }; + }; +} diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts new file mode 100644 index 000000000..8646537ff --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts @@ -0,0 +1,60 @@ +import { + DAppRequestHandler, + DAppProviderRequest, + JsonRpcRequestParams, + CURRENCIES, +} from '@core/types'; +import { injectable } from 'tsyringe'; +import { SettingsService } from '../SettingsService'; + +type Params = [currency?: string]; + +// Avalanche add account handler +// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts +@injectable() +export class AvalancheSetCurrencyHandler extends DAppRequestHandler< + Params, + null +> { + methods = [DAppProviderRequest.AVALANCHE_SET_CURRENCY]; + + constructor(private settingsService: SettingsService) { + super(); + } + + handleAuthenticated = async ( + rpcCall: JsonRpcRequestParams, + ) => { + const { request } = rpcCall; + + try { + const [currency] = request.params || []; + + if (!currency) { + throw new Error('Empty currency parameter'); + } + const availableLanguages = Object.values(CURRENCIES); + if (!availableLanguages.includes(currency as CURRENCIES)) { + throw new Error('Invalid currency parameter'); + } + + await this.settingsService.setCurrencty(currency as CURRENCIES); + return { + ...request, + result: currency, + }; + } catch (e: any) { + return { + ...request, + error: e.toString(), + }; + } + }; + + handleUnauthenticated = ({ request }) => { + return { + ...request, + error: 'account not connected', + }; + }; +} diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts new file mode 100644 index 000000000..336e28d1b --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts @@ -0,0 +1,60 @@ +import { + DAppRequestHandler, + DAppProviderRequest, + JsonRpcRequestParams, + Languages, +} from '@core/types'; +import { injectable } from 'tsyringe'; +import { SettingsService } from '../SettingsService'; + +type Params = [language?: string]; + +// Avalanche add account handler +// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts +@injectable() +export class AvalancheSetLanguageHandler extends DAppRequestHandler< + Params, + null +> { + methods = [DAppProviderRequest.AVALANCHE_SET_LANGUAGE]; + + constructor(private settingsService: SettingsService) { + super(); + } + + handleAuthenticated = async ( + rpcCall: JsonRpcRequestParams, + ) => { + const { request } = rpcCall; + + try { + const [language] = request.params || []; + + if (!language) { + throw new Error('Empty language parameter'); + } + const availableLanguages = Object.values(Languages); + if (!availableLanguages.includes(language as Languages)) { + throw new Error('Invalid language parameter'); + } + + await this.settingsService.setLanguage(language as Languages); + return { + ...request, + result: language, + }; + } catch (e: any) { + return { + ...request, + error: e.toString(), + }; + } + }; + + handleUnauthenticated = ({ request }) => { + return { + ...request, + error: 'account not connected', + }; + }; +} diff --git a/packages/types/src/dapp-connection.ts b/packages/types/src/dapp-connection.ts index 8d5ad3a7a..e451f6b45 100644 --- a/packages/types/src/dapp-connection.ts +++ b/packages/types/src/dapp-connection.ts @@ -47,6 +47,9 @@ export enum DAppProviderRequest { WALLET_GET_PUBKEY = 'wallet_getPublicKey', WALLET_CONNECT = 'wallet_requestAccountPermission', WALLET_GET_NETWORK_STATE = 'wallet_getNetworkState', + AVALANCHE_SET_LANGUAGE = 'avalanche_setLanguage', + AVALANCHE_SET_CURRENCY = 'avalanche_setCurrency', + AVALANCHE_GET_SETTINGS = 'avalanche_getSettings', } export enum Web3Event { From b33e73ac69b69a690e5df3a9ed9653a3abb378f6 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 30 Oct 2025 22:22:47 +0100 Subject: [PATCH 2/8] chore: remove unnecessary lines --- .../src/services/settings/handlers/avalanche_getSettings.ts | 3 --- .../src/services/settings/handlers/avalanche_setCurrency.ts | 2 -- .../src/services/settings/handlers/avalanche_setLanguage.ts | 2 -- 3 files changed, 7 deletions(-) diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts index 3ab4e80b0..ef36a9dd8 100644 --- a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts +++ b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts @@ -2,8 +2,6 @@ import { DAppRequestHandler, DAppProviderRequest } from '@core/types'; import { injectable } from 'tsyringe'; import { SettingsService } from '../SettingsService'; -// Avalanche add account handler -// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts @injectable() export class AvalancheGetSettingsHandler extends DAppRequestHandler { methods = [DAppProviderRequest.AVALANCHE_GET_SETTINGS]; @@ -15,7 +13,6 @@ export class AvalancheGetSettingsHandler extends DAppRequestHandler { handleAuthenticated = async ({ request }) => { try { const settings = await this.settingsService.getSettings(); - console.log('settings: ', settings); return { ...request, result: settings, diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts index 8646537ff..6121ca9e4 100644 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts @@ -9,8 +9,6 @@ import { SettingsService } from '../SettingsService'; type Params = [currency?: string]; -// Avalanche add account handler -// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts @injectable() export class AvalancheSetCurrencyHandler extends DAppRequestHandler< Params, diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts index 336e28d1b..e107c741b 100644 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts @@ -9,8 +9,6 @@ import { SettingsService } from '../SettingsService'; type Params = [language?: string]; -// Avalanche add account handler -// This handler is similar to the AddAccountHandler, but it is used for dapp adding accounts to primary accounts @injectable() export class AvalancheSetLanguageHandler extends DAppRequestHandler< Params, From 27bd32261aad6392899b999c11da7e1913a34b8f Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 08:51:35 +0100 Subject: [PATCH 3/8] test: cases for handlers --- .../handlers/avalanche_getSettings.test.ts | 284 ++++++++++++++++++ .../handlers/avalanche_setCurrency.test.ts | 186 ++++++++++++ .../handlers/avalanche_setLanguage.test.ts | 146 +++++++++ 3 files changed, 616 insertions(+) create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts create mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts new file mode 100644 index 000000000..a4da61f78 --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts @@ -0,0 +1,284 @@ +import { + DAppProviderRequest, + Languages, + CURRENCIES, + SettingsState, + AnalyticsConsent, +} from '@core/types'; +import { AvalancheGetSettingsHandler } from './avalanche_getSettings'; +import { buildRpcCall } from '@shared/tests/test-utils'; +import { SettingsService } from '../SettingsService'; + +describe('packages/service-worker/src/services/settings/handlers/avalanche_getSettings', () => { + const getSettingsMock = jest.fn(); + const settingsServiceMock = { + getSettings: getSettingsMock, + } as unknown as SettingsService; + + const handler = new AvalancheGetSettingsHandler(settingsServiceMock); + + const createRequest = () => ({ + id: '123', + method: DAppProviderRequest.AVALANCHE_GET_SETTINGS, + }); + + const mockSettingsState: SettingsState = { + currency: CURRENCIES.USD, + customTokens: {}, + showTokensWithoutBalances: true, + theme: 'LIGHT', + tokensVisibility: {}, + collectiblesVisibility: {}, + analyticsConsent: AnalyticsConsent.Approved, + language: Languages.EN, + coreAssistant: true, + preferredView: 'floating', + showTrendingTokens: true, + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('handleAuthenticated', () => { + it('should successfully return settings', async () => { + const request = createRequest(); + getSettingsMock.mockResolvedValueOnce(mockSettingsState); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(getSettingsMock).toHaveBeenCalledTimes(1); + expect(getSettingsMock).toHaveBeenCalledWith(); + expect(result).toEqual({ + ...request, + result: mockSettingsState, + }); + }); + + it('should return settings with EUR currency', async () => { + const request = createRequest(); + const settingsWithEur = { + ...mockSettingsState, + currency: CURRENCIES.EUR, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithEur); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithEur, + }); + }); + + it('should return settings with dark theme', async () => { + const request = createRequest(); + const settingsWithDarkTheme = { + ...mockSettingsState, + theme: 'DARK', + }; + getSettingsMock.mockResolvedValueOnce(settingsWithDarkTheme); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithDarkTheme, + }); + }); + + it('should return settings with different language', async () => { + const request = createRequest(); + const settingsWithSpanish = { + ...mockSettingsState, + language: Languages.ES, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithSpanish); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithSpanish, + }); + }); + + it('should return settings with showTokensWithoutBalances false', async () => { + const request = createRequest(); + const settingsWithHiddenTokens = { + ...mockSettingsState, + showTokensWithoutBalances: false, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithHiddenTokens); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithHiddenTokens, + }); + }); + + it('should return settings with coreAssistant disabled', async () => { + const request = createRequest(); + const settingsWithoutAssistant = { + ...mockSettingsState, + coreAssistant: false, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithoutAssistant); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithoutAssistant, + }); + }); + + it('should return settings with showTrendingTokens false', async () => { + const request = createRequest(); + const settingsWithoutTrending = { + ...mockSettingsState, + showTrendingTokens: false, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithoutTrending); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithoutTrending, + }); + }); + + it('should return settings with analytics consent denied', async () => { + const request = createRequest(); + const settingsWithDeniedConsent = { + ...mockSettingsState, + analyticsConsent: AnalyticsConsent.Denied, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithDeniedConsent); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithDeniedConsent, + }); + }); + + it('should return settings with analytics consent pending', async () => { + const request = createRequest(); + const settingsWithPendingConsent = { + ...mockSettingsState, + analyticsConsent: AnalyticsConsent.Pending, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithPendingConsent); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithPendingConsent, + }); + }); + + it('should return settings with custom tokens', async () => { + const request = createRequest(); + const settingsWithCustomTokens = { + ...mockSettingsState, + customTokens: { + '43114': { + '0x123': { + address: '0x123', + symbol: 'TEST', + decimals: 18, + }, + }, + }, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithCustomTokens); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithCustomTokens, + }); + }); + + it('should return settings with tokens visibility configuration', async () => { + const request = createRequest(); + const settingsWithVisibility = { + ...mockSettingsState, + tokensVisibility: { + '43114': { + '0x123': false, + }, + }, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithVisibility); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithVisibility, + }); + }); + + it('should return settings with collectibles visibility configuration', async () => { + const request = createRequest(); + const settingsWithCollectiblesVisibility = { + ...mockSettingsState, + collectiblesVisibility: { + '43114': { + '0xabc': false, + }, + }, + }; + getSettingsMock.mockResolvedValueOnce(settingsWithCollectiblesVisibility); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + result: settingsWithCollectiblesVisibility, + }); + }); + + it('should return error when settingsService.getSettings throws an error', async () => { + const request = createRequest(); + const error = new Error('Failed to retrieve settings'); + getSettingsMock.mockRejectedValueOnce(error); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(getSettingsMock).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + ...request, + error: error.toString(), + }); + }); + }); + + describe('handleUnauthenticated', () => { + it('should return error when account is not connected', async () => { + const request = createRequest(); + const result = await handler.handleUnauthenticated(buildRpcCall(request)); + + expect(getSettingsMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'account not connected', + }); + }); + + it('should not call getSettings when unauthenticated', async () => { + const request = createRequest(); + await handler.handleUnauthenticated(buildRpcCall(request)); + + expect(getSettingsMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts new file mode 100644 index 000000000..95582f0ab --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts @@ -0,0 +1,186 @@ +import { DAppProviderRequest, CURRENCIES } from '@core/types'; +import { AvalancheSetCurrencyHandler } from './avalanche_setCurrency'; +import { buildRpcCall } from '@shared/tests/test-utils'; +import { SettingsService } from '../SettingsService'; + +describe('packages/service-worker/src/services/settings/handlers/avalanche_setCurrency', () => { + const setCurrencyMock = jest.fn(); + const settingsServiceMock = { + setCurrencty: setCurrencyMock, // Note: matches the typo in the actual code + } as unknown as SettingsService; + + const handler = new AvalancheSetCurrencyHandler(settingsServiceMock); + + const createRequest = (params?: [string?]) => ({ + id: '123', + method: DAppProviderRequest.AVALANCHE_SET_CURRENCY, + params, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('handleAuthenticated', () => { + it('should successfully set a valid currency', async () => { + const request = createRequest([CURRENCIES.USD]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.USD); + expect(setCurrencyMock).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + ...request, + result: CURRENCIES.USD, + }); + }); + + it('should successfully set all valid currencies', async () => { + const validCurrencies = Object.values(CURRENCIES); + + for (const currency of validCurrencies) { + jest.resetAllMocks(); + const request = createRequest([currency]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(currency); + expect(result).toEqual({ + ...request, + result: currency, + }); + } + }); + + it('should successfully set EUR currency', async () => { + const request = createRequest([CURRENCIES.EUR]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.EUR); + expect(result).toEqual({ + ...request, + result: CURRENCIES.EUR, + }); + }); + + it('should successfully set GBP currency', async () => { + const request = createRequest([CURRENCIES.GBP]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.GBP); + expect(result).toEqual({ + ...request, + result: CURRENCIES.GBP, + }); + }); + + it('should return error when currency parameter is empty', async () => { + const request = createRequest([]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty currency parameter', + }); + }); + + it('should return error when currency parameter is undefined', async () => { + const request = createRequest([undefined]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty currency parameter', + }); + }); + + it('should return error when params array is undefined', async () => { + const request = createRequest(undefined); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty currency parameter', + }); + }); + + it('should return error when currency parameter is invalid', async () => { + const request = createRequest(['INVALID']); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid currency parameter', + }); + }); + + it('should return error when currency parameter is an empty string', async () => { + const request = createRequest(['']); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty currency parameter', + }); + }); + + it('should return error when currency code is lowercase', async () => { + const request = createRequest(['usd']); // Wrong case - should be USD + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid currency parameter', + }); + }); + + it('should return error when settingsService.setCurrency throws an error', async () => { + const request = createRequest([CURRENCIES.USD]); + const error = new Error('Failed to save currency'); + setCurrencyMock.mockRejectedValueOnce(error); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.USD); + expect(result).toEqual({ + ...request, + error: error.toString(), + }); + }); + }); + + describe('handleUnauthenticated', () => { + it('should return error when account is not connected', async () => { + const request = createRequest([CURRENCIES.USD]); + const result = await handler.handleUnauthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'account not connected', + }); + }); + + it('should return error when account is not connected regardless of currency', async () => { + const validCurrencies = Object.values(CURRENCIES); + + for (const currency of validCurrencies) { + jest.resetAllMocks(); + const request = createRequest([currency]); + const result = await handler.handleUnauthenticated( + buildRpcCall(request), + ); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'account not connected', + }); + } + }); + }); +}); diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts new file mode 100644 index 000000000..32be228f3 --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts @@ -0,0 +1,146 @@ +import { DAppProviderRequest, Languages } from '@core/types'; +import { AvalancheSetLanguageHandler } from './avalanche_setLanguage'; +import { buildRpcCall } from '@shared/tests/test-utils'; +import { SettingsService } from '../SettingsService'; + +describe('packages/service-worker/src/services/settings/handlers/avalanche_setLanguage', () => { + const setLanguageMock = jest.fn(); + const settingsServiceMock = { + setLanguage: setLanguageMock, + } as unknown as SettingsService; + + const handler = new AvalancheSetLanguageHandler(settingsServiceMock); + + const createRequest = (params?: [string?]) => ({ + id: '123', + method: DAppProviderRequest.AVALANCHE_SET_LANGUAGE, + params, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('handleAuthenticated', () => { + it('should successfully set a valid language', async () => { + const request = createRequest([Languages.EN]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).toHaveBeenCalledWith(Languages.EN); + expect(setLanguageMock).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + ...request, + result: Languages.EN, + }); + }); + + it('should successfully set all valid languages', async () => { + const validLanguages = Object.values(Languages); + + for (const language of validLanguages) { + jest.resetAllMocks(); + const request = createRequest([language]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).toHaveBeenCalledWith(language); + expect(result).toEqual({ + ...request, + result: language, + }); + } + }); + + it('should return error when language parameter is empty', async () => { + const request = createRequest([]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty language parameter', + }); + }); + + it('should return error when language parameter is undefined', async () => { + const request = createRequest([undefined]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty language parameter', + }); + }); + + it('should return error when params array is undefined', async () => { + const request = createRequest(undefined); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty language parameter', + }); + }); + + it('should return error when language parameter is invalid', async () => { + const request = createRequest(['invalid-language']); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid language parameter', + }); + }); + + it('should return error when language parameter is an empty string', async () => { + const request = createRequest(['']); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Empty language parameter', + }); + }); + + it('should return error when settingsService.setLanguage throws an error', async () => { + const request = createRequest([Languages.EN]); + const error = new Error('Failed to save language'); + setLanguageMock.mockRejectedValueOnce(error); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).toHaveBeenCalledWith(Languages.EN); + expect(result).toEqual({ + ...request, + error: error.toString(), + }); + }); + + it('should handle case-sensitive language codes correctly', async () => { + const request = createRequest(['EN']); // Wrong case + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid language parameter', + }); + }); + }); + + describe('handleUnauthenticated', () => { + it('should return error when account is not connected', async () => { + const request = createRequest([Languages.EN]); + const result = await handler.handleUnauthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'account not connected', + }); + }); + }); +}); From 60ed81752c38ac7a73bbc24f640cb6d0f7ec0373 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 09:20:06 +0100 Subject: [PATCH 4/8] feat: add settings updated web3 event --- .../connections/dAppConnection/registry.ts | 2 ++ .../settings/events/settingsUpdatedEvent.ts | 29 ++++++++++++++++--- packages/types/src/dapp-connection.ts | 3 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/service-worker/src/connections/dAppConnection/registry.ts b/packages/service-worker/src/connections/dAppConnection/registry.ts index 5276c602e..e62d3aa34 100644 --- a/packages/service-worker/src/connections/dAppConnection/registry.ts +++ b/packages/service-worker/src/connections/dAppConnection/registry.ts @@ -42,6 +42,7 @@ import { NetworkStateChangedEvents } from '~/services/network/events/networkStat import { AvalancheSetLanguageHandler } from '~/services/settings/handlers/avalanche_setLanguage'; import { AvalancheGetSettingsHandler } from '~/services/settings/handlers/avalanche_getSettings'; import { AvalancheSetCurrencyHandler } from '~/services/settings/handlers/avalanche_setCurrency'; +import { SettingsUpdatedEvents } from '~/services/settings/events/settingsUpdatedEvent'; /** * TODO: GENERATE THIS FILE AS PART OF THE BUILD PROCESS @@ -137,5 +138,6 @@ export class DappRequestHandlerRegistry {} { token: 'DAppEventEmitter', useToken: ChainChangedEvents }, { token: 'DAppEventEmitter', useToken: ActionEvents }, { token: 'DAppEventEmitter', useToken: NetworkStateChangedEvents }, + { token: 'DAppEventEmitter', useToken: SettingsUpdatedEvents }, ]) export class DappEventEmitterRegistry {} diff --git a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts index 4df69af2b..a5d29f7b4 100644 --- a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts +++ b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts @@ -1,23 +1,44 @@ import { + ConnectionInfo, + DAppEventEmitter, ExtensionConnectionEvent, - ExtensionEventEmitter, SettingsEvents, + Web3Event, } from '@core/types'; +import { isSyncDomain } from '@core/common'; import { EventEmitter } from 'events'; import { SettingsService } from '../SettingsService'; -import { singleton } from 'tsyringe'; +import { injectable } from 'tsyringe'; -@singleton() -export class SettingsUpdatedEvents implements ExtensionEventEmitter { +@injectable() +export class SettingsUpdatedEvents implements DAppEventEmitter { private eventEmitter = new EventEmitter(); + private _connectionInfo?: ConnectionInfo; + + setConnectionInfo(connectionInfo: ConnectionInfo) { + this._connectionInfo = connectionInfo; + } + constructor(private settingsService: SettingsService) { this.settingsService.addListener( SettingsEvents.SETTINGS_UPDATED, (settings) => { + // Emit extension event (for extension UI) this.eventEmitter.emit('update', { name: SettingsEvents.SETTINGS_UPDATED, value: settings, }); + + // Emit web3 event (for dApps) - only for Core Suite domains + if ( + this._connectionInfo?.domain && + isSyncDomain(this._connectionInfo.domain) + ) { + this.eventEmitter.emit('update', { + method: Web3Event.SETTINGS_CHANGED, + params: settings, + }); + } }, ); } diff --git a/packages/types/src/dapp-connection.ts b/packages/types/src/dapp-connection.ts index e451f6b45..40580ee4d 100644 --- a/packages/types/src/dapp-connection.ts +++ b/packages/types/src/dapp-connection.ts @@ -65,8 +65,9 @@ export enum Web3Event { // https://eips.ethereum.org/EIPS/eip-1193#chainchanged-1 CHAIN_CHANGED = 'chainChanged', - // Core Web specific event + // Core Web specific events NETWORK_STATE_CHANGED = 'networkStateChanged', + SETTINGS_CHANGED = 'settingsChanged', } export interface JsonRpcRequestParams { From 8c502434be9d51906751a4f6f73f69e269559461 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 14:26:35 +0100 Subject: [PATCH 5/8] feat: add separated core event handler --- .../connections/dAppConnection/registry.ts | 4 +- .../settings/events/settingsUpdatedEvent.ts | 17 +------ .../events/settingsUpdatedEventCore.ts | 49 +++++++++++++++++++ 3 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 packages/service-worker/src/services/settings/events/settingsUpdatedEventCore.ts diff --git a/packages/service-worker/src/connections/dAppConnection/registry.ts b/packages/service-worker/src/connections/dAppConnection/registry.ts index e62d3aa34..a6a69eaa0 100644 --- a/packages/service-worker/src/connections/dAppConnection/registry.ts +++ b/packages/service-worker/src/connections/dAppConnection/registry.ts @@ -42,7 +42,7 @@ import { NetworkStateChangedEvents } from '~/services/network/events/networkStat import { AvalancheSetLanguageHandler } from '~/services/settings/handlers/avalanche_setLanguage'; import { AvalancheGetSettingsHandler } from '~/services/settings/handlers/avalanche_getSettings'; import { AvalancheSetCurrencyHandler } from '~/services/settings/handlers/avalanche_setCurrency'; -import { SettingsUpdatedEvents } from '~/services/settings/events/settingsUpdatedEvent'; +import { SettingsUpdatedEventsCore } from '~/services/settings/events/settingsUpdatedEventCore'; /** * TODO: GENERATE THIS FILE AS PART OF THE BUILD PROCESS @@ -138,6 +138,6 @@ export class DappRequestHandlerRegistry {} { token: 'DAppEventEmitter', useToken: ChainChangedEvents }, { token: 'DAppEventEmitter', useToken: ActionEvents }, { token: 'DAppEventEmitter', useToken: NetworkStateChangedEvents }, - { token: 'DAppEventEmitter', useToken: SettingsUpdatedEvents }, + { token: 'DAppEventEmitter', useToken: SettingsUpdatedEventsCore }, ]) export class DappEventEmitterRegistry {} diff --git a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts index a5d29f7b4..7b649083c 100644 --- a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts +++ b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts @@ -3,14 +3,12 @@ import { DAppEventEmitter, ExtensionConnectionEvent, SettingsEvents, - Web3Event, } from '@core/types'; -import { isSyncDomain } from '@core/common'; import { EventEmitter } from 'events'; import { SettingsService } from '../SettingsService'; -import { injectable } from 'tsyringe'; +import { singleton } from 'tsyringe'; -@injectable() +@singleton() export class SettingsUpdatedEvents implements DAppEventEmitter { private eventEmitter = new EventEmitter(); private _connectionInfo?: ConnectionInfo; @@ -28,17 +26,6 @@ export class SettingsUpdatedEvents implements DAppEventEmitter { name: SettingsEvents.SETTINGS_UPDATED, value: settings, }); - - // Emit web3 event (for dApps) - only for Core Suite domains - if ( - this._connectionInfo?.domain && - isSyncDomain(this._connectionInfo.domain) - ) { - this.eventEmitter.emit('update', { - method: Web3Event.SETTINGS_CHANGED, - params: settings, - }); - } }, ); } diff --git a/packages/service-worker/src/services/settings/events/settingsUpdatedEventCore.ts b/packages/service-worker/src/services/settings/events/settingsUpdatedEventCore.ts new file mode 100644 index 000000000..093290330 --- /dev/null +++ b/packages/service-worker/src/services/settings/events/settingsUpdatedEventCore.ts @@ -0,0 +1,49 @@ +import { + ConnectionInfo, + DAppEventEmitter, + ExtensionConnectionEvent, + SettingsEvents, + Web3Event, +} from '@core/types'; +import { isSyncDomain } from '@core/common'; +import { EventEmitter } from 'events'; +import { SettingsService } from '../SettingsService'; +import { singleton } from 'tsyringe'; + +@singleton() +export class SettingsUpdatedEventsCore implements DAppEventEmitter { + private eventEmitter = new EventEmitter(); + private _connectionInfo?: ConnectionInfo; + + setConnectionInfo(connectionInfo: ConnectionInfo) { + this._connectionInfo = connectionInfo; + } + + constructor(private settingsService: SettingsService) { + this.settingsService.addListener( + SettingsEvents.SETTINGS_UPDATED, + (settings) => { + // Emit web3 event (for dApps) - only for Core Suite domains + if ( + this._connectionInfo?.domain && + isSyncDomain(this._connectionInfo.domain) + ) { + this.eventEmitter.emit('update', { + method: Web3Event.SETTINGS_CHANGED, + params: settings, + }); + } + }, + ); + } + + addListener(handler: (event: ExtensionConnectionEvent) => void): void { + this.eventEmitter.on('update', handler); + } + + removeListener( + handler: (event: ExtensionConnectionEvent) => void, + ): void { + this.eventEmitter.off('update', handler); + } +} From ce9d95e1b8d6a6b29c1d6495f37084270bbbcdd6 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 14:31:32 +0100 Subject: [PATCH 6/8] refactor: remove dappEventEmitter --- .../services/settings/events/settingsUpdatedEvent.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts index 7b649083c..4df69af2b 100644 --- a/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts +++ b/packages/service-worker/src/services/settings/events/settingsUpdatedEvent.ts @@ -1,7 +1,6 @@ import { - ConnectionInfo, - DAppEventEmitter, ExtensionConnectionEvent, + ExtensionEventEmitter, SettingsEvents, } from '@core/types'; import { EventEmitter } from 'events'; @@ -9,19 +8,12 @@ import { SettingsService } from '../SettingsService'; import { singleton } from 'tsyringe'; @singleton() -export class SettingsUpdatedEvents implements DAppEventEmitter { +export class SettingsUpdatedEvents implements ExtensionEventEmitter { private eventEmitter = new EventEmitter(); - private _connectionInfo?: ConnectionInfo; - - setConnectionInfo(connectionInfo: ConnectionInfo) { - this._connectionInfo = connectionInfo; - } - constructor(private settingsService: SettingsService) { this.settingsService.addListener( SettingsEvents.SETTINGS_UPDATED, (settings) => { - // Emit extension event (for extension UI) this.eventEmitter.emit('update', { name: SettingsEvents.SETTINGS_UPDATED, value: settings, From 8a5e2533ea8bdccb3a1b5e261a86908382feddb9 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 17:34:26 +0100 Subject: [PATCH 7/8] feat: replace setCurrency and lang to setSeetings --- .../connections/dAppConnection/registry.ts | 10 +- .../middlewares/PermissionMiddleware.ts | 5 +- .../handlers/avalanche_setCurrency.test.ts | 186 ------------ .../handlers/avalanche_setCurrency.ts | 58 ---- .../handlers/avalanche_setLanguage.test.ts | 146 --------- .../handlers/avalanche_setLanguage.ts | 58 ---- ...ngs.test.ts => wallet_getSettings.test.ts} | 6 +- ...e_getSettings.ts => wallet_getSettings.ts} | 4 +- .../handlers/wallet_setSettings.test.ts | 283 ++++++++++++++++++ .../settings/handlers/wallet_setSettings.ts | 193 ++++++++++++ packages/types/src/dapp-connection.ts | 5 +- 11 files changed, 489 insertions(+), 465 deletions(-) delete mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts delete mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts delete mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts delete mode 100644 packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts rename packages/service-worker/src/services/settings/handlers/{avalanche_getSettings.test.ts => wallet_getSettings.test.ts} (97%) rename packages/service-worker/src/services/settings/handlers/{avalanche_getSettings.ts => wallet_getSettings.ts} (84%) create mode 100644 packages/service-worker/src/services/settings/handlers/wallet_setSettings.test.ts create mode 100644 packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts diff --git a/packages/service-worker/src/connections/dAppConnection/registry.ts b/packages/service-worker/src/connections/dAppConnection/registry.ts index a6a69eaa0..26f36d33a 100644 --- a/packages/service-worker/src/connections/dAppConnection/registry.ts +++ b/packages/service-worker/src/connections/dAppConnection/registry.ts @@ -39,10 +39,9 @@ import { AccountsChangedCAEvents } from '../../services/accounts/events/accounts import { RequestAccountPermissionHandler } from '../../services/web3/handlers/wallet_requestAccountPermission'; import { WalletGetNetworkStateHandler } from '~/services/network/handlers/wallet_getNetworkState'; import { NetworkStateChangedEvents } from '~/services/network/events/networkStateChanged'; -import { AvalancheSetLanguageHandler } from '~/services/settings/handlers/avalanche_setLanguage'; -import { AvalancheGetSettingsHandler } from '~/services/settings/handlers/avalanche_getSettings'; -import { AvalancheSetCurrencyHandler } from '~/services/settings/handlers/avalanche_setCurrency'; import { SettingsUpdatedEventsCore } from '~/services/settings/events/settingsUpdatedEventCore'; +import { WalletSetSettingsHandler } from '~/services/settings/handlers/wallet_setSettings'; +import { WalletGetSettingsHandler } from '~/services/settings/handlers/wallet_getSettings'; /** * TODO: GENERATE THIS FILE AS PART OF THE BUILD PROCESS @@ -92,9 +91,8 @@ const SHARED_HANDLERS = [ token: 'DAppRequestHandler', useToken: WalletGetNetworkStateHandler, }, - { token: 'DAppRequestHandler', useToken: AvalancheSetLanguageHandler }, - { token: 'DAppRequestHandler', useToken: AvalancheGetSettingsHandler }, - { token: 'DAppRequestHandler', useToken: AvalancheSetCurrencyHandler }, + { token: 'DAppRequestHandler', useToken: WalletGetSettingsHandler }, + { token: 'DAppRequestHandler', useToken: WalletSetSettingsHandler }, ]; const LEGACY_REQUEST_HANDLERS = [ diff --git a/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts b/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts index 4f2861228..4287bae4d 100644 --- a/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts +++ b/packages/service-worker/src/connections/middlewares/PermissionMiddleware.ts @@ -119,9 +119,8 @@ const CORE_METHODS = Object.freeze([ DAppProviderRequest.BITCOIN_SEND_TRANSACTION, DAppProviderRequest.WALLET_RENAME, DAppProviderRequest.WALLET_GET_NETWORK_STATE, - DAppProviderRequest.AVALANCHE_SET_LANGUAGE, - DAppProviderRequest.AVALANCHE_GET_SETTINGS, - DAppProviderRequest.AVALANCHE_SET_CURRENCY, + DAppProviderRequest.WALLET_GET_SETTINGS, + DAppProviderRequest.WALLET_SET_SETTINGS, ]); export function PermissionMiddleware( diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts deleted file mode 100644 index 95582f0ab..000000000 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { DAppProviderRequest, CURRENCIES } from '@core/types'; -import { AvalancheSetCurrencyHandler } from './avalanche_setCurrency'; -import { buildRpcCall } from '@shared/tests/test-utils'; -import { SettingsService } from '../SettingsService'; - -describe('packages/service-worker/src/services/settings/handlers/avalanche_setCurrency', () => { - const setCurrencyMock = jest.fn(); - const settingsServiceMock = { - setCurrencty: setCurrencyMock, // Note: matches the typo in the actual code - } as unknown as SettingsService; - - const handler = new AvalancheSetCurrencyHandler(settingsServiceMock); - - const createRequest = (params?: [string?]) => ({ - id: '123', - method: DAppProviderRequest.AVALANCHE_SET_CURRENCY, - params, - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('handleAuthenticated', () => { - it('should successfully set a valid currency', async () => { - const request = createRequest([CURRENCIES.USD]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.USD); - expect(setCurrencyMock).toHaveBeenCalledTimes(1); - expect(result).toEqual({ - ...request, - result: CURRENCIES.USD, - }); - }); - - it('should successfully set all valid currencies', async () => { - const validCurrencies = Object.values(CURRENCIES); - - for (const currency of validCurrencies) { - jest.resetAllMocks(); - const request = createRequest([currency]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).toHaveBeenCalledWith(currency); - expect(result).toEqual({ - ...request, - result: currency, - }); - } - }); - - it('should successfully set EUR currency', async () => { - const request = createRequest([CURRENCIES.EUR]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.EUR); - expect(result).toEqual({ - ...request, - result: CURRENCIES.EUR, - }); - }); - - it('should successfully set GBP currency', async () => { - const request = createRequest([CURRENCIES.GBP]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.GBP); - expect(result).toEqual({ - ...request, - result: CURRENCIES.GBP, - }); - }); - - it('should return error when currency parameter is empty', async () => { - const request = createRequest([]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty currency parameter', - }); - }); - - it('should return error when currency parameter is undefined', async () => { - const request = createRequest([undefined]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty currency parameter', - }); - }); - - it('should return error when params array is undefined', async () => { - const request = createRequest(undefined); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty currency parameter', - }); - }); - - it('should return error when currency parameter is invalid', async () => { - const request = createRequest(['INVALID']); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Invalid currency parameter', - }); - }); - - it('should return error when currency parameter is an empty string', async () => { - const request = createRequest(['']); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty currency parameter', - }); - }); - - it('should return error when currency code is lowercase', async () => { - const request = createRequest(['usd']); // Wrong case - should be USD - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Invalid currency parameter', - }); - }); - - it('should return error when settingsService.setCurrency throws an error', async () => { - const request = createRequest([CURRENCIES.USD]); - const error = new Error('Failed to save currency'); - setCurrencyMock.mockRejectedValueOnce(error); - - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.USD); - expect(result).toEqual({ - ...request, - error: error.toString(), - }); - }); - }); - - describe('handleUnauthenticated', () => { - it('should return error when account is not connected', async () => { - const request = createRequest([CURRENCIES.USD]); - const result = await handler.handleUnauthenticated(buildRpcCall(request)); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'account not connected', - }); - }); - - it('should return error when account is not connected regardless of currency', async () => { - const validCurrencies = Object.values(CURRENCIES); - - for (const currency of validCurrencies) { - jest.resetAllMocks(); - const request = createRequest([currency]); - const result = await handler.handleUnauthenticated( - buildRpcCall(request), - ); - - expect(setCurrencyMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'account not connected', - }); - } - }); - }); -}); diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts deleted file mode 100644 index 6121ca9e4..000000000 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setCurrency.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - DAppRequestHandler, - DAppProviderRequest, - JsonRpcRequestParams, - CURRENCIES, -} from '@core/types'; -import { injectable } from 'tsyringe'; -import { SettingsService } from '../SettingsService'; - -type Params = [currency?: string]; - -@injectable() -export class AvalancheSetCurrencyHandler extends DAppRequestHandler< - Params, - null -> { - methods = [DAppProviderRequest.AVALANCHE_SET_CURRENCY]; - - constructor(private settingsService: SettingsService) { - super(); - } - - handleAuthenticated = async ( - rpcCall: JsonRpcRequestParams, - ) => { - const { request } = rpcCall; - - try { - const [currency] = request.params || []; - - if (!currency) { - throw new Error('Empty currency parameter'); - } - const availableLanguages = Object.values(CURRENCIES); - if (!availableLanguages.includes(currency as CURRENCIES)) { - throw new Error('Invalid currency parameter'); - } - - await this.settingsService.setCurrencty(currency as CURRENCIES); - return { - ...request, - result: currency, - }; - } catch (e: any) { - return { - ...request, - error: e.toString(), - }; - } - }; - - handleUnauthenticated = ({ request }) => { - return { - ...request, - error: 'account not connected', - }; - }; -} diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts deleted file mode 100644 index 32be228f3..000000000 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { DAppProviderRequest, Languages } from '@core/types'; -import { AvalancheSetLanguageHandler } from './avalanche_setLanguage'; -import { buildRpcCall } from '@shared/tests/test-utils'; -import { SettingsService } from '../SettingsService'; - -describe('packages/service-worker/src/services/settings/handlers/avalanche_setLanguage', () => { - const setLanguageMock = jest.fn(); - const settingsServiceMock = { - setLanguage: setLanguageMock, - } as unknown as SettingsService; - - const handler = new AvalancheSetLanguageHandler(settingsServiceMock); - - const createRequest = (params?: [string?]) => ({ - id: '123', - method: DAppProviderRequest.AVALANCHE_SET_LANGUAGE, - params, - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('handleAuthenticated', () => { - it('should successfully set a valid language', async () => { - const request = createRequest([Languages.EN]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).toHaveBeenCalledWith(Languages.EN); - expect(setLanguageMock).toHaveBeenCalledTimes(1); - expect(result).toEqual({ - ...request, - result: Languages.EN, - }); - }); - - it('should successfully set all valid languages', async () => { - const validLanguages = Object.values(Languages); - - for (const language of validLanguages) { - jest.resetAllMocks(); - const request = createRequest([language]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).toHaveBeenCalledWith(language); - expect(result).toEqual({ - ...request, - result: language, - }); - } - }); - - it('should return error when language parameter is empty', async () => { - const request = createRequest([]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty language parameter', - }); - }); - - it('should return error when language parameter is undefined', async () => { - const request = createRequest([undefined]); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty language parameter', - }); - }); - - it('should return error when params array is undefined', async () => { - const request = createRequest(undefined); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty language parameter', - }); - }); - - it('should return error when language parameter is invalid', async () => { - const request = createRequest(['invalid-language']); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Invalid language parameter', - }); - }); - - it('should return error when language parameter is an empty string', async () => { - const request = createRequest(['']); - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Empty language parameter', - }); - }); - - it('should return error when settingsService.setLanguage throws an error', async () => { - const request = createRequest([Languages.EN]); - const error = new Error('Failed to save language'); - setLanguageMock.mockRejectedValueOnce(error); - - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).toHaveBeenCalledWith(Languages.EN); - expect(result).toEqual({ - ...request, - error: error.toString(), - }); - }); - - it('should handle case-sensitive language codes correctly', async () => { - const request = createRequest(['EN']); // Wrong case - const result = await handler.handleAuthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'Error: Invalid language parameter', - }); - }); - }); - - describe('handleUnauthenticated', () => { - it('should return error when account is not connected', async () => { - const request = createRequest([Languages.EN]); - const result = await handler.handleUnauthenticated(buildRpcCall(request)); - - expect(setLanguageMock).not.toHaveBeenCalled(); - expect(result).toEqual({ - ...request, - error: 'account not connected', - }); - }); - }); -}); diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts b/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts deleted file mode 100644 index e107c741b..000000000 --- a/packages/service-worker/src/services/settings/handlers/avalanche_setLanguage.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - DAppRequestHandler, - DAppProviderRequest, - JsonRpcRequestParams, - Languages, -} from '@core/types'; -import { injectable } from 'tsyringe'; -import { SettingsService } from '../SettingsService'; - -type Params = [language?: string]; - -@injectable() -export class AvalancheSetLanguageHandler extends DAppRequestHandler< - Params, - null -> { - methods = [DAppProviderRequest.AVALANCHE_SET_LANGUAGE]; - - constructor(private settingsService: SettingsService) { - super(); - } - - handleAuthenticated = async ( - rpcCall: JsonRpcRequestParams, - ) => { - const { request } = rpcCall; - - try { - const [language] = request.params || []; - - if (!language) { - throw new Error('Empty language parameter'); - } - const availableLanguages = Object.values(Languages); - if (!availableLanguages.includes(language as Languages)) { - throw new Error('Invalid language parameter'); - } - - await this.settingsService.setLanguage(language as Languages); - return { - ...request, - result: language, - }; - } catch (e: any) { - return { - ...request, - error: e.toString(), - }; - } - }; - - handleUnauthenticated = ({ request }) => { - return { - ...request, - error: 'account not connected', - }; - }; -} diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.test.ts similarity index 97% rename from packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts rename to packages/service-worker/src/services/settings/handlers/wallet_getSettings.test.ts index a4da61f78..0396c3857 100644 --- a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.test.ts +++ b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.test.ts @@ -5,7 +5,7 @@ import { SettingsState, AnalyticsConsent, } from '@core/types'; -import { AvalancheGetSettingsHandler } from './avalanche_getSettings'; +import { WalletGetSettingsHandler } from './wallet_getSettings'; import { buildRpcCall } from '@shared/tests/test-utils'; import { SettingsService } from '../SettingsService'; @@ -15,11 +15,11 @@ describe('packages/service-worker/src/services/settings/handlers/avalanche_getSe getSettings: getSettingsMock, } as unknown as SettingsService; - const handler = new AvalancheGetSettingsHandler(settingsServiceMock); + const handler = new WalletGetSettingsHandler(settingsServiceMock); const createRequest = () => ({ id: '123', - method: DAppProviderRequest.AVALANCHE_GET_SETTINGS, + method: DAppProviderRequest.WALLET_GET_SETTINGS, }); const mockSettingsState: SettingsState = { diff --git a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts similarity index 84% rename from packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts rename to packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts index ef36a9dd8..4d51b4e70 100644 --- a/packages/service-worker/src/services/settings/handlers/avalanche_getSettings.ts +++ b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts @@ -3,8 +3,8 @@ import { injectable } from 'tsyringe'; import { SettingsService } from '../SettingsService'; @injectable() -export class AvalancheGetSettingsHandler extends DAppRequestHandler { - methods = [DAppProviderRequest.AVALANCHE_GET_SETTINGS]; +export class WalletGetSettingsHandler extends DAppRequestHandler { + methods = [DAppProviderRequest.WALLET_GET_SETTINGS]; constructor(private settingsService: SettingsService) { super(); diff --git a/packages/service-worker/src/services/settings/handlers/wallet_setSettings.test.ts b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.test.ts new file mode 100644 index 000000000..685602a66 --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.test.ts @@ -0,0 +1,283 @@ +import { + DAppProviderRequest, + Languages, + CURRENCIES, + SettingsState, + AnalyticsConsent, + ColorTheme, +} from '@core/types'; +import { WalletSetSettingsHandler } from './wallet_setSettings'; +import { buildRpcCall } from '@shared/tests/test-utils'; +import { SettingsService } from '../SettingsService'; + +describe('packages/service-worker/src/services/settings/handlers/wallet_setSettings', () => { + const getSettingsMock = jest.fn(); + const setLanguageMock = jest.fn(); + const setCurrencyMock = jest.fn(); + const setShowTokensWithNoBalanceMock = jest.fn(); + const setThemeMock = jest.fn(); + const setTokensVisibilityMock = jest.fn(); + const setCollectiblesVisibilityMock = jest.fn(); + const setAnalyticsConsentMock = jest.fn(); + const setCoreAssistantMock = jest.fn(); + const setPreferredViewMock = jest.fn(); + const setShowTrendingTokensMock = jest.fn(); + + const settingsServiceMock = { + getSettings: getSettingsMock, + setLanguage: setLanguageMock, + setCurrencty: setCurrencyMock, + setShowTokensWithNoBalance: setShowTokensWithNoBalanceMock, + setTheme: setThemeMock, + setTokensVisibility: setTokensVisibilityMock, + setCollectiblesVisibility: setCollectiblesVisibilityMock, + setAnalyticsConsent: setAnalyticsConsentMock, + setCoreAssistant: setCoreAssistantMock, + setPreferredView: setPreferredViewMock, + setShowTrendingTokens: setShowTrendingTokensMock, + } as unknown as SettingsService; + + const handler = new WalletSetSettingsHandler(settingsServiceMock); + + const mockSettingsState: SettingsState = { + currency: CURRENCIES.USD, + customTokens: {}, + showTokensWithoutBalances: true, + theme: 'LIGHT', + tokensVisibility: {}, + collectiblesVisibility: {}, + analyticsConsent: AnalyticsConsent.Approved, + language: Languages.EN, + coreAssistant: true, + preferredView: 'floating', + showTrendingTokens: true, + }; + + const createRequest = (params?: [Partial?]) => ({ + id: '123', + method: DAppProviderRequest.WALLET_SET_SETTINGS, + params, + }); + + beforeEach(() => { + jest.resetAllMocks(); + getSettingsMock.mockResolvedValue(mockSettingsState); + }); + + describe('handleAuthenticated', () => { + it('should successfully update language setting', async () => { + const request = createRequest([{ language: Languages.ES }]); + const updatedSettings = { ...mockSettingsState, language: Languages.ES }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).toHaveBeenCalledWith(Languages.ES); + expect(setLanguageMock).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should successfully update currency setting', async () => { + const request = createRequest([{ currency: CURRENCIES.EUR }]); + const updatedSettings = { + ...mockSettingsState, + currency: CURRENCIES.EUR, + }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.EUR); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should successfully update theme setting', async () => { + const request = createRequest([{ theme: 'DARK' }]); + const updatedSettings = { ...mockSettingsState, theme: 'DARK' }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setThemeMock).toHaveBeenCalledWith('DARK'); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should successfully update multiple settings', async () => { + const request = createRequest([ + { + language: Languages.FR, + currency: CURRENCIES.EUR, + theme: 'DARK', + coreAssistant: false, + }, + ]); + const updatedSettings = { + ...mockSettingsState, + language: Languages.FR, + currency: CURRENCIES.EUR, + theme: 'DARK', + coreAssistant: false, + }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).toHaveBeenCalledWith(Languages.FR); + expect(setCurrencyMock).toHaveBeenCalledWith(CURRENCIES.EUR); + expect(setThemeMock).toHaveBeenCalledWith('DARK'); + expect(setCoreAssistantMock).toHaveBeenCalledWith(false); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should successfully update showTokensWithoutBalances', async () => { + const request = createRequest([{ showTokensWithoutBalances: false }]); + const updatedSettings = { + ...mockSettingsState, + showTokensWithoutBalances: false, + }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setShowTokensWithNoBalanceMock).toHaveBeenCalledWith(false); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should successfully update analytics consent', async () => { + const request = createRequest([ + { analyticsConsent: AnalyticsConsent.Denied }, + ]); + const updatedSettings = { + ...mockSettingsState, + analyticsConsent: AnalyticsConsent.Denied, + }; + getSettingsMock.mockResolvedValueOnce(updatedSettings); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setAnalyticsConsentMock).toHaveBeenCalledWith(false); + expect(result).toEqual({ + ...request, + result: updatedSettings, + }); + }); + + it('should return error when no settings provided', async () => { + const request = createRequest([]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + error: 'Error: No settings provided', + }); + }); + + it('should return error when settings object is empty', async () => { + const request = createRequest([{}]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + error: 'Error: No settings provided', + }); + }); + + it('should return error when params are undefined', async () => { + const request = createRequest(undefined); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + error: 'Error: No settings provided', + }); + }); + + it('should return error for invalid language', async () => { + const request = createRequest([{ language: 'invalid' as Languages }]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid language parameter', + }); + }); + + it('should return error for invalid currency', async () => { + const request = createRequest([{ currency: 'INVALID' as CURRENCIES }]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setCurrencyMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid currency parameter', + }); + }); + + it('should return error for invalid theme', async () => { + const request = createRequest([{ theme: 'INVALID' as ColorTheme }]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setThemeMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid theme parameter', + }); + }); + + it('should return error for invalid analyticsConsent', async () => { + const request = createRequest([ + { analyticsConsent: 'invalid' as AnalyticsConsent }, + ]); + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(setAnalyticsConsentMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'Error: Invalid analyticsConsent parameter', + }); + }); + + it('should return error when service throws an error', async () => { + const request = createRequest([{ language: Languages.EN }]); + const error = new Error('Failed to save settings'); + setLanguageMock.mockRejectedValueOnce(error); + + const result = await handler.handleAuthenticated(buildRpcCall(request)); + + expect(result).toEqual({ + ...request, + error: error.toString(), + }); + }); + }); + + describe('handleUnauthenticated', () => { + it('should return error when account is not connected', async () => { + const request = createRequest([{ language: Languages.EN }]); + const result = await handler.handleUnauthenticated(buildRpcCall(request)); + + expect(getSettingsMock).not.toHaveBeenCalled(); + expect(setLanguageMock).not.toHaveBeenCalled(); + expect(result).toEqual({ + ...request, + error: 'account not connected', + }); + }); + }); +}); diff --git a/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts new file mode 100644 index 000000000..ba0128dac --- /dev/null +++ b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts @@ -0,0 +1,193 @@ +import { + DAppRequestHandler, + DAppProviderRequest, + JsonRpcRequestParams, + SettingsState, + Languages, + CURRENCIES, + AnalyticsConsent, + ColorTheme, + ViewMode, +} from '@core/types'; +import { injectable } from 'tsyringe'; +import { SettingsService } from '../SettingsService'; + +type PartialSettings = Partial; +type Params = [settings?: PartialSettings]; + +@injectable() +export class WalletSetSettingsHandler extends DAppRequestHandler< + Params, + SettingsState +> { + methods = [DAppProviderRequest.WALLET_SET_SETTINGS]; + + constructor(private settingsService: SettingsService) { + super(); + } + + private validateSettings(settings: PartialSettings): { + valid: boolean; + error?: string; + } { + // Validate language if provided + if (settings.language !== undefined) { + const availableLanguages = Object.values(Languages); + if (!availableLanguages.includes(settings.language)) { + return { valid: false, error: 'Invalid language parameter' }; + } + } + + // Validate currency if provided + if (settings.currency !== undefined) { + const availableCurrencies = Object.values(CURRENCIES); + if (!availableCurrencies.includes(settings.currency as CURRENCIES)) { + return { valid: false, error: 'Invalid currency parameter' }; + } + } + + // Validate analyticsConsent if provided + if (settings.analyticsConsent !== undefined) { + const validConsents = Object.values(AnalyticsConsent); + if (!validConsents.includes(settings.analyticsConsent)) { + return { valid: false, error: 'Invalid analyticsConsent parameter' }; + } + } + + // Validate theme if provided + if (settings.theme !== undefined) { + const validThemes = ['LIGHT', 'DARK', 'SYSTEM'] as ColorTheme[]; + if (!validThemes.includes(settings.theme)) { + return { valid: false, error: 'Invalid theme parameter' }; + } + } + + // Validate preferredView if provided + if (settings.preferredView !== undefined) { + const validViews = ['floating', 'sidebar'] as ViewMode[]; + if (!validViews.includes(settings.preferredView)) { + return { valid: false, error: 'Invalid preferredView parameter' }; + } + } + + // Validate showTokensWithoutBalances if provided + if ( + settings.showTokensWithoutBalances !== undefined && + typeof settings.showTokensWithoutBalances !== 'boolean' + ) { + return { + valid: false, + error: 'Invalid showTokensWithoutBalances parameter', + }; + } + + // Validate coreAssistant if provided + if ( + settings.coreAssistant !== undefined && + typeof settings.coreAssistant !== 'boolean' + ) { + return { valid: false, error: 'Invalid coreAssistant parameter' }; + } + + // Validate showTrendingTokens if provided + if ( + settings.showTrendingTokens !== undefined && + typeof settings.showTrendingTokens !== 'boolean' + ) { + return { valid: false, error: 'Invalid showTrendingTokens parameter' }; + } + + return { valid: true }; + } + + handleAuthenticated = async ( + rpcCall: JsonRpcRequestParams, + ) => { + const { request } = rpcCall; + + try { + const [newSettings] = request.params || []; + + if (!newSettings || Object.keys(newSettings).length === 0) { + throw new Error('No settings provided'); + } + + // Validate the settings + const validation = this.validateSettings(newSettings); + if (!validation.valid) { + throw new Error(validation.error); + } + + // Update each setting individually using service methods + if (newSettings.language !== undefined) { + await this.settingsService.setLanguage(newSettings.language); + } + + if (newSettings.currency !== undefined) { + await this.settingsService.setCurrencty(newSettings.currency); + } + + if (newSettings.showTokensWithoutBalances !== undefined) { + await this.settingsService.setShowTokensWithNoBalance( + newSettings.showTokensWithoutBalances, + ); + } + + if (newSettings.theme !== undefined) { + await this.settingsService.setTheme(newSettings.theme); + } + + if (newSettings.tokensVisibility !== undefined) { + await this.settingsService.setTokensVisibility( + newSettings.tokensVisibility, + ); + } + + if (newSettings.collectiblesVisibility !== undefined) { + await this.settingsService.setCollectiblesVisibility( + newSettings.collectiblesVisibility, + ); + } + + if (newSettings.analyticsConsent !== undefined) { + await this.settingsService.setAnalyticsConsent( + newSettings.analyticsConsent === AnalyticsConsent.Approved, + ); + } + + if (newSettings.coreAssistant !== undefined) { + await this.settingsService.setCoreAssistant(newSettings.coreAssistant); + } + + if (newSettings.preferredView !== undefined) { + await this.settingsService.setPreferredView(newSettings.preferredView); + } + + if (newSettings.showTrendingTokens !== undefined) { + await this.settingsService.setShowTrendingTokens( + newSettings.showTrendingTokens, + ); + } + + // Get the final updated settings + const finalSettings = await this.settingsService.getSettings(); + + return { + ...request, + result: finalSettings, + }; + } catch (e: any) { + return { + ...request, + error: e.toString(), + }; + } + }; + + handleUnauthenticated = ({ request }) => { + return { + ...request, + error: 'account not connected', + }; + }; +} diff --git a/packages/types/src/dapp-connection.ts b/packages/types/src/dapp-connection.ts index 40580ee4d..653f7b2f1 100644 --- a/packages/types/src/dapp-connection.ts +++ b/packages/types/src/dapp-connection.ts @@ -47,9 +47,8 @@ export enum DAppProviderRequest { WALLET_GET_PUBKEY = 'wallet_getPublicKey', WALLET_CONNECT = 'wallet_requestAccountPermission', WALLET_GET_NETWORK_STATE = 'wallet_getNetworkState', - AVALANCHE_SET_LANGUAGE = 'avalanche_setLanguage', - AVALANCHE_SET_CURRENCY = 'avalanche_setCurrency', - AVALANCHE_GET_SETTINGS = 'avalanche_getSettings', + WALLET_GET_SETTINGS = 'wallet_getSettings', + WALLET_SET_SETTINGS = 'wallet_setSettings', } export enum Web3Event { From 102b61ae1b79f09013028dabbfed7cd4b8962e22 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Fri, 31 Oct 2025 18:23:37 +0100 Subject: [PATCH 8/8] refactor: types --- .../settings/handlers/wallet_getSettings.ts | 50 +++++++++++++++++-- .../settings/handlers/wallet_setSettings.ts | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts index 4d51b4e70..d74c27dfe 100644 --- a/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts +++ b/packages/service-worker/src/services/settings/handlers/wallet_getSettings.ts @@ -1,9 +1,41 @@ -import { DAppRequestHandler, DAppProviderRequest } from '@core/types'; +import { NetworkContractToken } from '@avalabs/core-chains-sdk'; +import { + DAppRequestHandler, + DAppProviderRequest, + ColorTheme, + TokensVisibility, + CollectiblesVisibility, + AnalyticsConsent, + Languages, + ViewMode, +} from '@core/types'; import { injectable } from 'tsyringe'; import { SettingsService } from '../SettingsService'; +type CustomTokens = { + [networkCaipId: string]: { + [tokenAddress: string]: NetworkContractToken; + }; +}; + +interface WalletGetSettingsHandlerResult { + currency: string; + customTokens: CustomTokens; + showTokensWithoutBalances: boolean; + theme: ColorTheme; + tokensVisibility: TokensVisibility; + collectiblesVisibility: CollectiblesVisibility; + analyticsConsent: AnalyticsConsent; + language: Languages; + coreAssistant: boolean; + preferredView: ViewMode; + showTrendingTokens: boolean; +} @injectable() -export class WalletGetSettingsHandler extends DAppRequestHandler { +export class WalletGetSettingsHandler extends DAppRequestHandler< + [], + WalletGetSettingsHandlerResult +> { methods = [DAppProviderRequest.WALLET_GET_SETTINGS]; constructor(private settingsService: SettingsService) { @@ -15,7 +47,19 @@ export class WalletGetSettingsHandler extends DAppRequestHandler { const settings = await this.settingsService.getSettings(); return { ...request, - result: settings, + result: { + currency: settings.currency, + customTokens: settings.customTokens, + showTokensWithoutBalances: settings.showTokensWithoutBalances, + theme: settings.theme, + tokensVisibility: settings.tokensVisibility, + collectiblesVisibility: settings.collectiblesVisibility, + analyticsConsent: settings.analyticsConsent, + language: settings.language, + coreAssistant: settings.coreAssistant, + preferredView: settings.preferredView, + showTrendingTokens: settings.showTrendingTokens, + }, }; } catch (e: any) { return { diff --git a/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts index ba0128dac..444c46029 100644 --- a/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts +++ b/packages/service-worker/src/services/settings/handlers/wallet_setSettings.ts @@ -12,7 +12,7 @@ import { import { injectable } from 'tsyringe'; import { SettingsService } from '../SettingsService'; -type PartialSettings = Partial; +type PartialSettings = Omit; type Params = [settings?: PartialSettings]; @injectable()