feat(suite-common): add geolocation condition to the message system

This commit is contained in:
Jaroslav Hrách
2025-05-31 01:32:58 +02:00
committed by Tomáš Klíma
parent fa21b2c05b
commit e8c5a56570
20 changed files with 655 additions and 16 deletions

View File

@@ -281,7 +281,8 @@ Structure of config, types and optionality of specific keys can be found in the
"duration": {
"from": "2021-03-01T12:10:00.000Z",
"to": "2022-01-31T12:10:00.000Z"
}
},
"countryCodes": ["CZ", "US"]
}
],
// Detail of an experiment

View File

@@ -151,6 +151,7 @@ export const getJWSPublicKey = (use: JWSPublicKeyUse) => {
return isCodesignBuild() ? firmwareConfigPublicKey.codesign : firmwareConfigPublicKey.dev;
};
export const envUtils: EnvUtils = {
isWeb,
isDesktop,

View File

@@ -38,3 +38,4 @@ export const REQUEST_DEVICE_RECONNECT = '@suite/request-device-reconnect';
export const SET_EXPERIMENTAL_FEATURES = '@suite/set-experimental-features';
export const SET_SIDEBAR_WIDTH = '@suite/set-sidebar-width';
export const SET_IS_COINS_FILTER_VISIBLE = '@suite/set-is-coins-filter-visible';
export const SET_COUNTRY_CODE = '@suite/set-country-code';

View File

@@ -1,7 +1,7 @@
import { createAction } from '@reduxjs/toolkit';
import { notificationsActions } from '@suite-common/toast-notifications';
import type { TradingType } from '@suite-common/trading';
import type { CountryCode, TradingType } from '@suite-common/trading';
import { deviceActions } from '@suite-common/wallet-core';
import { getCustomBackends } from '@suite-common/wallet-utils';
import { EventType, analytics } from '@trezor/suite-analytics';
@@ -62,6 +62,7 @@ export type SuiteAction =
symbol: keyof EvmSettings['explanationBannerClosed'];
}
| { type: typeof SUITE.DISMISSED_TRADING_TERMS; tradingType: TradingType }
| { type: typeof SUITE.SET_COUNTRY_CODE; payload: CountryCode }
| { type: typeof SUITE.APP_CHANGED; payload: AppState['router']['app'] }
| {
type: typeof SUITE.SET_THEME;
@@ -166,6 +167,11 @@ export const setDismissedTradingTerms = (tradingType: TradingType): SuiteAction
tradingType,
});
export const setCountryCode = (countryCode: CountryCode): SuiteAction => ({
type: SUITE.SET_COUNTRY_CODE,
payload: countryCode,
});
/**
* Triggered by `@suite-support/OnlineStatus` or `@suite-native/support/OnlineStatus`
* Set `online` status in suite reducer

View File

@@ -246,6 +246,7 @@ export const getRootReducer = (selectedAccount = BTC_ACCOUNT, fees = DEFAULT_FEE
dismissedTradingTerms: {},
prefillFields: { sendForm: '', transactionHistory: '' },
flags: { stakeEthBannerClosed: false, stakeSolBannerClosed: false },
countryCode: null,
},
() => ({}),
),

View File

@@ -9,7 +9,7 @@ import { changeNetworks, deviceActions, selectSelectedDevice } from '@suite-comm
import { DEVICE, TRANSPORT } from '@trezor/connect';
import { SUITE } from 'src/actions/suite/constants';
import { selectActiveTransports } from 'src/reducers/suite/suiteReducer';
import { selectActiveTransports, selectCountryCode } from 'src/reducers/suite/suiteReducer';
import { getIsTorEnabled } from 'src/utils/suite/tor';
// actions which can affect message system messages
@@ -20,6 +20,7 @@ const actions = [
changeNetworks.type,
TRANSPORT.START,
DEVICE.CONNECT,
SUITE.SET_COUNTRY_CODE,
];
const messageSystemMiddleware = createMiddleware(async (action, { next, dispatch, getState }) => {
@@ -31,6 +32,7 @@ const messageSystemMiddleware = createMiddleware(async (action, { next, dispatch
const transports = selectActiveTransports(getState());
const device = selectSelectedDevice(getState());
const { enabledNetworks } = getState().wallet.settings;
const countryCode = selectCountryCode(getState());
const validationParams = {
device,
@@ -39,6 +41,7 @@ const messageSystemMiddleware = createMiddleware(async (action, { next, dispatch
tor: getIsTorEnabled(torStatus),
enabledNetworks,
},
countryCode,
};
const [validMessages, validExperimentIds] = await Promise.all([

View File

@@ -2,12 +2,18 @@ import { MiddlewareAPI } from 'redux';
import { ROUTER } from 'src/actions/suite/constants';
import { Action, AppState, Dispatch } from 'src/types/suite';
import { fetchCountryCodeThunk, shouldFetchCountryCode } from 'src/utils/suite/countryCode';
const router = (api: MiddlewareAPI<Dispatch, AppState>) => (next: Dispatch) => (action: Action) => {
const { router } = api.getState();
const { router, suite } = api.getState();
switch (action.type) {
case ROUTER.LOCATION_CHANGE:
case ROUTER.LOCATION_CHANGE: {
// Fetch country code only when entering trading or staking routes
if (suite.countryCode == null && shouldFetchCountryCode(action.payload.route?.name)) {
api.dispatch(fetchCountryCodeThunk());
}
/**
* Store back route for navigation when closing the settings.
* Exclude settings routes we want to close the settings and not just switch the settings tab...
@@ -28,6 +34,7 @@ const router = (api: MiddlewareAPI<Dispatch, AppState>) => (next: Dispatch) => (
}
break;
}
default:
break;
}

View File

@@ -2,7 +2,7 @@ import { produce } from 'immer';
import { Feature, selectIsFeatureDisabled } from '@suite-common/message-system';
import { isDeviceAcquired } from '@suite-common/suite-utils';
import type { InvityServerEnvironment, TradingType } from '@suite-common/trading';
import type { CountryCode, InvityServerEnvironment, TradingType } from '@suite-common/trading';
import { NetworkSymbol } from '@suite-common/wallet-config';
import {
DeviceRootState,
@@ -137,6 +137,7 @@ export interface SuiteState {
flags: Flags;
evmSettings: EvmSettings;
dismissedTradingTerms: Partial<Record<TradingType, boolean>>;
countryCode: CountryCode | null;
prefillFields: PrefillFields;
settings: SuiteSettings;
}
@@ -183,6 +184,7 @@ const initialState: SuiteState = {
sendForm: '',
transactionHistory: '',
},
countryCode: null,
settings: {
theme: {
variant: 'light',
@@ -307,6 +309,9 @@ const suiteReducer = (state: SuiteState = initialState, action: Action): SuiteSt
[action.tradingType]: true,
};
break;
case SUITE.SET_COUNTRY_CODE:
draft.countryCode = action.payload;
break;
case SUITE.SET_THEME:
draft.settings.theme.variant = action.variant;
break;
@@ -528,6 +533,8 @@ export const selectIsFirmwareHashCheckEnabled = (state: SuiteRootState) =>
export const selectIsFirmwareRevisionCheckEnabled = (state: SuiteRootState) =>
state.suite.settings.enabledSecurityChecks.firmwareRevision;
export const selectCountryCode = (state: SuiteRootState) => state.suite.countryCode;
/**
* Get firmware revision check error, or null if check was successful / skipped.
*/

View File

@@ -0,0 +1,37 @@
import { createThunk } from '@suite-common/redux-utils';
import { CountryCode } from '@suite-common/trading';
import { GEOLOCATION_API_URL } from '@trezor/urls';
import { setCountryCode } from 'src/actions/suite/suiteActions';
const GEOLOCATION_PREFIX = '@suite/geolocation';
type GeolocationResponse = {
country: string;
};
export const shouldFetchCountryCode = (routeName: string | undefined) => {
if (!routeName) return false;
const isTradingRoute = routeName.includes('wallet-trading');
const isStakingRoute = routeName.includes('staking');
return isTradingRoute || isStakingRoute;
};
export const fetchCountryCodeThunk = createThunk<void, void, void>(
`${GEOLOCATION_PREFIX}/fetchCountryCodeThunk`,
async (_, { dispatch }) => {
try {
const response = await fetch(GEOLOCATION_API_URL);
const data = (await response.json()) as GeolocationResponse;
if (typeof data?.country === 'string') {
const code = data.country.trim().toUpperCase() as unknown as CountryCode;
dispatch(setCountryCode(code));
}
} catch {
// silently fail
}
},
);

View File

@@ -173,3 +173,5 @@ export const ESHOP_KEEP_METAL_MULTI_SHARE_URL: Url =
export const TRADING_DOWNLOAD_INVITY_APP_URL: Url = 'https://get.invity.io';
export const UNINSTALL_BRIDGE_URL: Url = 'https://trezor.io/learn/a/what-is-trezor-bridge';
export const GEOLOCATION_API_URL = 'https://suite-geolocation.admin9940.workers.dev/get-country/';

View File

@@ -476,10 +476,274 @@
},
"additionalProperties": false
}
},
"countryCodes": {
"title": "Country code",
"description": "Target users by country code (ISO 3166-1 alpha-2).",
"type": "array",
"items": {
"$ref": "#/definitions/countryCodes"
},
"minItems": 1,
"uniqueItems": true
}
},
"additionalProperties": false
}
},
"countryCodes": {
"type": "string",
"enum": [
"AD",
"AE",
"AF",
"AG",
"AI",
"AL",
"AM",
"AO",
"AQ",
"AR",
"AS",
"AT",
"AU",
"AW",
"AX",
"AZ",
"BA",
"BB",
"BD",
"BE",
"BF",
"BG",
"BH",
"BI",
"BJ",
"BL",
"BM",
"BN",
"BO",
"BQ",
"BR",
"BS",
"BT",
"BV",
"BW",
"BY",
"BZ",
"CA",
"CC",
"CD",
"CF",
"CG",
"CH",
"CI",
"CK",
"CL",
"CM",
"CN",
"CO",
"CR",
"CU",
"CV",
"CW",
"CX",
"CY",
"CZ",
"DE",
"DJ",
"DK",
"DM",
"DO",
"DZ",
"EC",
"EE",
"EG",
"EH",
"ER",
"ES",
"ET",
"FI",
"FJ",
"FK",
"FM",
"FO",
"FR",
"GA",
"GB",
"GD",
"GE",
"GF",
"GG",
"GH",
"GI",
"GL",
"GM",
"GN",
"GP",
"GQ",
"GR",
"GS",
"GT",
"GU",
"GW",
"GY",
"HK",
"HM",
"HN",
"HR",
"HT",
"HU",
"ID",
"IE",
"IL",
"IM",
"IN",
"IO",
"IQ",
"IR",
"IS",
"IT",
"JE",
"JM",
"JO",
"JP",
"KE",
"KG",
"KH",
"KI",
"KM",
"KN",
"KP",
"KR",
"KW",
"KY",
"KZ",
"LA",
"LB",
"LC",
"LI",
"LK",
"LR",
"LS",
"LT",
"LU",
"LV",
"LY",
"MA",
"MC",
"MD",
"ME",
"MF",
"MG",
"MH",
"MK",
"ML",
"MM",
"MN",
"MO",
"MP",
"MQ",
"MR",
"MS",
"MT",
"MU",
"MV",
"MW",
"MX",
"MY",
"MZ",
"NA",
"NC",
"NE",
"NF",
"NG",
"NI",
"NL",
"NO",
"NP",
"NR",
"NU",
"NZ",
"OM",
"PA",
"PE",
"PF",
"PG",
"PH",
"PK",
"PL",
"PM",
"PN",
"PR",
"PS",
"PT",
"PW",
"PY",
"QA",
"RE",
"RO",
"RS",
"RU",
"RW",
"SA",
"SB",
"SC",
"SD",
"SE",
"SG",
"SH",
"SI",
"SJ",
"SK",
"SL",
"SM",
"SN",
"SO",
"SR",
"SS",
"ST",
"SV",
"SX",
"SY",
"SZ",
"TC",
"TD",
"TF",
"TG",
"TH",
"TJ",
"TK",
"TL",
"TM",
"TN",
"TO",
"TR",
"TT",
"TV",
"TW",
"TZ",
"UA",
"UG",
"UM",
"US",
"UY",
"UZ",
"VA",
"VC",
"VE",
"VG",
"VI",
"VN",
"VU",
"WF",
"WS",
"YE",
"YT",
"ZA",
"ZM",
"ZW"
]
}
}
}

View File

@@ -8,7 +8,10 @@ import { Options } from '../messageSystemUtils';
const { getDeviceFeatures, getConnectDevice, getMessageSystemConfig } = testMocks;
const defaultOptions: Options = { settings: { tor: false, enabledNetworks: ['btc'] } };
const defaultOptions: Options = {
settings: { tor: false, enabledNetworks: ['btc'] },
countryCode: 'US',
};
const defaultTransportsOption: TransportInfo = {
type: 'BridgeTransport',
apiType: 'usb',
@@ -1428,7 +1431,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
},
],
}),
options: { settings: { tor: false, enabledNetworks: [] } },
options: { settings: { tor: false, enabledNetworks: [] }, countryCode: 'US' },
result: [getMessageSystemConfig().actions[1].message],
},
{
@@ -1451,7 +1454,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
},
],
}),
options: { settings: { tor: false, enabledNetworks: [] } },
options: { settings: { tor: false, enabledNetworks: [] }, countryCode: 'US' },
result: [],
},
{
@@ -1585,7 +1588,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
},
],
}),
options: { settings: { tor: false, enabledNetworks: [] } },
options: { settings: { tor: false, enabledNetworks: [] }, countryCode: 'US' },
result: [],
},
{
@@ -1603,7 +1606,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
},
],
}),
options: { settings: { tor: false, enabledNetworks: ['btc'] } },
options: { settings: { tor: false, enabledNetworks: ['btc'] }, countryCode: 'US' },
result: [getMessageSystemConfig().actions[1].message],
},
{
@@ -1627,6 +1630,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
options: {
settings: { tor: false, enabledNetworks: [] },
transports: [{ ...defaultTransportsOption, version: '2.3.4' }],
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},
@@ -1651,6 +1655,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
options: {
settings: { tor: false, enabledNetworks: [] },
transports: [{ ...defaultTransportsOption, version: '2.3.4' }],
countryCode: 'US',
},
result: [],
},
@@ -1689,6 +1694,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
options: {
settings: { tor: false, enabledNetworks: [] },
device: getConnectAcquiredDevice(),
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},
@@ -1719,6 +1725,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
options: {
settings: { tor: false, enabledNetworks: [] },
device: getConnectAcquiredDevice(),
countryCode: 'US',
},
result: [],
},
@@ -1753,6 +1760,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
capabilities: ['Capability_Bitcoin'],
}),
},
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},
@@ -1787,6 +1795,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
capabilities: ['Capability_Bitcoin_like'],
}),
},
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},
@@ -1803,6 +1812,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
settings: { tor: true, enabledNetworks: ['btc'] },
transports: [{ ...defaultTransportsOption, version: '2.0.30' }],
device: getConnectAcquiredDevice(),
countryCode: 'US',
},
result: getMessageSystemConfig().actions.map(action => action.message),
},
@@ -1843,6 +1853,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
patch_version: 4,
}),
},
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},
@@ -1860,6 +1871,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
settings: { tor: true, enabledNetworks: ['btc'] },
transports: [{ ...defaultTransportsOption, version: '2.0.30' }],
device: getConnectAcquiredDevice(),
countryCode: 'US',
},
result: getMessageSystemConfig().actions.map(action => action.message),
},
@@ -1877,6 +1889,7 @@ export const getValidMessages: GetValidMessagesFixture[] = [
settings: { tor: true, enabledNetworks: ['btc'] },
transports: [{ ...defaultTransportsOption, version: '2.0.30' }],
device: getConnectAcquiredDevice(),
countryCode: 'US',
},
result: [getMessageSystemConfig().actions[1].message],
},

View File

@@ -19,6 +19,8 @@ const initialState: MessageSystemState = {
dismissedMessages: {},
validExperiments: [],
countryCode: null,
};
export const messageSystemPersistedWhitelist: Array<keyof MessageSystemState> = [

View File

@@ -22,6 +22,8 @@ export const selectMessageSystemTimestamp = (state: MessageSystemRootState) =>
export const selectMessageSystemCurrentSequence = (state: MessageSystemRootState) =>
state.messageSystem.currentSequence;
export const selectCountryCode = (state: MessageSystemRootState) => state.messageSystem.countryCode;
const comparePriority = (a: Message, b: Message) => b.priority - a.priority;
const makeSelectActiveMessagesByCategory = (category: Category) =>

View File

@@ -1,4 +1,5 @@
import { Category, ExperimentsItem, MessageSystem } from '@suite-common/suite-types';
import { CountryCode } from '@suite-common/trading';
export type MessageState = { [key in Category]: boolean };
@@ -11,6 +12,7 @@ export type MessageSystemState = {
[key: string]: MessageState;
};
validExperiments: string[];
countryCode: CountryCode | null;
};
export type MessageSystemRootState = {

View File

@@ -12,6 +12,7 @@ import type {
TrezorDevice,
Version,
} from '@suite-common/suite-types';
import type { CountryCode } from '@suite-common/trading';
import type { NetworkSymbol } from '@suite-common/wallet-config';
import type { TransportInfo } from '@trezor/connect';
import {
@@ -64,6 +65,7 @@ export type Options = {
settings: CurrentSettings;
transports?: TransportInfo[];
device?: TrezorDevice;
countryCode: CountryCode | null;
};
/**
@@ -205,6 +207,19 @@ export const validateEnvironmentCompatibility = (
);
};
export const validateCountryCodeCompatibility = (
allowedCountryCodes: CountryCode[],
userCountryCode: CountryCode,
): boolean => {
if (!allowedCountryCodes.length) {
return true;
}
return allowedCountryCodes.some(
location => location.toUpperCase() === userCountryCode.toUpperCase(),
);
};
type EnvData = {
osName: ReturnType<typeof getOsName>;
osVersion: ReturnType<typeof transformVersionToSemverFormat>;
@@ -228,7 +243,7 @@ export const getEnvData = async (): Promise<EnvData> => ({
});
export const validateConditions = (condition: Condition, options: Options, envData: EnvData) => {
const { device, transports = [], settings } = options;
const { device, transports = [], settings, countryCode } = options;
const {
duration: durationCondition,
@@ -238,6 +253,7 @@ export const validateConditions = (condition: Condition, options: Options, envDa
transport: transportCondition,
settings: settingsCondition,
devices: deviceCondition,
countryCodes: countryCodeCondition,
} = condition;
if (durationCondition && !validateDurationCompatibility(durationCondition)) {
@@ -283,6 +299,13 @@ export const validateConditions = (condition: Condition, options: Options, envDa
return false;
}
if (
countryCodeCondition &&
(!countryCode || !validateCountryCodeCompatibility(countryCodeCondition, countryCode))
) {
return false;
}
return true;
};

View File

@@ -15,6 +15,262 @@ export type FirmwareVariant = '*' | 'bitcoin-only' | 'regular';
* Eligible authorized vendors.
*/
export type Vendor = '*' | 'trezor.io';
export type CountryCodes =
| 'AD'
| 'AE'
| 'AF'
| 'AG'
| 'AI'
| 'AL'
| 'AM'
| 'AO'
| 'AQ'
| 'AR'
| 'AS'
| 'AT'
| 'AU'
| 'AW'
| 'AX'
| 'AZ'
| 'BA'
| 'BB'
| 'BD'
| 'BE'
| 'BF'
| 'BG'
| 'BH'
| 'BI'
| 'BJ'
| 'BL'
| 'BM'
| 'BN'
| 'BO'
| 'BQ'
| 'BR'
| 'BS'
| 'BT'
| 'BV'
| 'BW'
| 'BY'
| 'BZ'
| 'CA'
| 'CC'
| 'CD'
| 'CF'
| 'CG'
| 'CH'
| 'CI'
| 'CK'
| 'CL'
| 'CM'
| 'CN'
| 'CO'
| 'CR'
| 'CU'
| 'CV'
| 'CW'
| 'CX'
| 'CY'
| 'CZ'
| 'DE'
| 'DJ'
| 'DK'
| 'DM'
| 'DO'
| 'DZ'
| 'EC'
| 'EE'
| 'EG'
| 'EH'
| 'ER'
| 'ES'
| 'ET'
| 'FI'
| 'FJ'
| 'FK'
| 'FM'
| 'FO'
| 'FR'
| 'GA'
| 'GB'
| 'GD'
| 'GE'
| 'GF'
| 'GG'
| 'GH'
| 'GI'
| 'GL'
| 'GM'
| 'GN'
| 'GP'
| 'GQ'
| 'GR'
| 'GS'
| 'GT'
| 'GU'
| 'GW'
| 'GY'
| 'HK'
| 'HM'
| 'HN'
| 'HR'
| 'HT'
| 'HU'
| 'ID'
| 'IE'
| 'IL'
| 'IM'
| 'IN'
| 'IO'
| 'IQ'
| 'IR'
| 'IS'
| 'IT'
| 'JE'
| 'JM'
| 'JO'
| 'JP'
| 'KE'
| 'KG'
| 'KH'
| 'KI'
| 'KM'
| 'KN'
| 'KP'
| 'KR'
| 'KW'
| 'KY'
| 'KZ'
| 'LA'
| 'LB'
| 'LC'
| 'LI'
| 'LK'
| 'LR'
| 'LS'
| 'LT'
| 'LU'
| 'LV'
| 'LY'
| 'MA'
| 'MC'
| 'MD'
| 'ME'
| 'MF'
| 'MG'
| 'MH'
| 'MK'
| 'ML'
| 'MM'
| 'MN'
| 'MO'
| 'MP'
| 'MQ'
| 'MR'
| 'MS'
| 'MT'
| 'MU'
| 'MV'
| 'MW'
| 'MX'
| 'MY'
| 'MZ'
| 'NA'
| 'NC'
| 'NE'
| 'NF'
| 'NG'
| 'NI'
| 'NL'
| 'NO'
| 'NP'
| 'NR'
| 'NU'
| 'NZ'
| 'OM'
| 'PA'
| 'PE'
| 'PF'
| 'PG'
| 'PH'
| 'PK'
| 'PL'
| 'PM'
| 'PN'
| 'PR'
| 'PS'
| 'PT'
| 'PW'
| 'PY'
| 'QA'
| 'RE'
| 'RO'
| 'RS'
| 'RU'
| 'RW'
| 'SA'
| 'SB'
| 'SC'
| 'SD'
| 'SE'
| 'SG'
| 'SH'
| 'SI'
| 'SJ'
| 'SK'
| 'SL'
| 'SM'
| 'SN'
| 'SO'
| 'SR'
| 'SS'
| 'ST'
| 'SV'
| 'SX'
| 'SY'
| 'SZ'
| 'TC'
| 'TD'
| 'TF'
| 'TG'
| 'TH'
| 'TJ'
| 'TK'
| 'TL'
| 'TM'
| 'TN'
| 'TO'
| 'TR'
| 'TT'
| 'TV'
| 'TW'
| 'TZ'
| 'UA'
| 'UG'
| 'UM'
| 'US'
| 'UY'
| 'UZ'
| 'VA'
| 'VC'
| 'VE'
| 'VG'
| 'VI'
| 'VN'
| 'VU'
| 'WF'
| 'WS'
| 'YE'
| 'YT'
| 'ZA'
| 'ZM'
| 'ZW';
/**
* Target users by country code (ISO 3166-1 alpha-2).
*
* @minItems 1
*/
export type CountryCode = CountryCodes[];
export type Conditions = Condition[];
export type Variant = 'info' | 'warning' | 'critical';
export type Category = 'banner' | 'context' | 'modal' | 'feature';
@@ -50,6 +306,7 @@ export interface Condition {
*/
settings?: Settings[];
devices?: Device[];
countryCodes?: CountryCode;
}
export interface Duration {
from: DateTime;

View File

@@ -3,7 +3,7 @@ import { isArrayMember } from '@trezor/utils';
class Regional {
readonly UNKNOWN_COUNTRY = 'unknown';
countries: [string, string][] = [
countries = [
[this.UNKNOWN_COUNTRY, `🌍 Worldwide`],
['AD', '🇦🇩 Andorra'],
['AE', '🇦🇪 United Arab Emirates'],
@@ -254,7 +254,7 @@ class Regional {
['ZA', '🇿🇦 South Africa'],
['ZM', '🇿🇲 Zambia'],
['ZW', '🇿🇼 Zimbabwe'],
];
] as const;
countriesMap = new Map<string, string>(this.countries);
@@ -309,6 +309,12 @@ class Regional {
}
}
type EEACountryCodes = (typeof regional.EEACountryCodes)[number];
export const regional = new Regional();
export type EEACountryCodes = (typeof regional.EEACountryCodes)[number];
export type OriginalCountryCode = (typeof regional.countries)[number][0];
// Add Cloudflare-specific codes "XX" (no country data) and "T1" (Tor network).
// See: https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ipcountry
export type CountryCode = Exclude<OriginalCountryCode, 'unknown'> | 'XX' | 'T1';

View File

@@ -5,6 +5,7 @@ import {
getValidExperimentIds,
getValidMessages,
messageSystemActions,
selectCountryCode,
selectMessageSystemConfig,
} from '@suite-common/message-system';
import { createMiddleware } from '@suite-common/redux-utils';
@@ -28,6 +29,7 @@ export const messageSystemMiddleware = createMiddleware(
const config = selectMessageSystemConfig(getState());
const device = selectSelectedDevice(getState());
const enabledNetworks = selectDeviceEnabledDiscoveryNetworkSymbols(getState());
const countryCode = selectCountryCode(getState());
const validationParams = {
device,
@@ -35,6 +37,7 @@ export const messageSystemMiddleware = createMiddleware(
tor: false, // not supported in suite-native
enabledNetworks,
},
countryCode,
};
const [validMessages, validExperimentIds] = await Promise.all([
getValidMessages(config, validationParams),

View File

@@ -89,6 +89,7 @@ const getPreloadedState = ({
},
dismissedMessages: {},
validExperiments: [],
countryCode: null,
},
};
};