From 00a3bd164f163a714d4e15e921dbb300b1fc70fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:40:43 +0000 Subject: [PATCH 01/30] setup metro bundler symlink resolution allow remote feature flag controller symlink to get bundled by metro --- metro.config.js | 18 ++++++++++++++++++ package.json | 1 + yarn.lock | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/metro.config.js b/metro.config.js index 4ea95a0a676..afc4373eb0f 100644 --- a/metro.config.js +++ b/metro.config.js @@ -8,6 +8,18 @@ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const path = require("path"); + +const featureFlagModuleDir = path.resolve(__dirname, "../../core/feature-flags/packages/remote-feature-flag-controller"); + +const extraNodeModules = { + "@metamask/remote-feature-flag-controller": featureFlagModuleDir, +}; + +const watchFolders = [ + featureFlagModuleDir, +]; + module.exports = function (baseConfig) { const defaultConfig = mergeConfig(baseConfig, getDefaultConfig(__dirname)); const { @@ -15,10 +27,16 @@ module.exports = function (baseConfig) { } = defaultConfig; return mergeConfig(defaultConfig, { + watchFolders, resolver: { assetExts: assetExts.filter((ext) => ext !== 'svg'), sourceExts: [...sourceExts, 'svg', 'cjs', 'mjs'], resolverMainFields: ['sbmodern', 'react-native', 'browser', 'main'], + extraNodeModules: new Proxy (extraNodeModules, { + get: (target, name) => + name in target ? target[name] : path.join(process.cwd(), `node_modules/${name}`), + }), + unstable_enableSymlinks: true, }, transformer: { babelTransformerPath: require.resolve('./metro.transform.js'), diff --git a/package.json b/package.json index c7c7f5b35f1..77bf2765a4b 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,7 @@ "@metamask/react-native-payments": "^2.0.0", "@metamask/react-native-search-api": "1.0.1", "@metamask/react-native-webview": "^14.0.4", + "@metamask/remote-feature-flag-controller": "link:../../core/feature-flags/packages/remote-feature-flag-controller", "@metamask/rpc-errors": "^7.0.1", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", diff --git a/yarn.lock b/yarn.lock index dde45e15ffc..fead3e924df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5345,6 +5345,10 @@ escape-string-regexp "^4.0.0" invariant "2.2.4" +"@metamask/remote-feature-flag-controller@link:../../core/feature-flags/packages/remote-feature-flag-controller": + version "0.0.0" + uid "" + "@metamask/rpc-errors@7.0.1", "@metamask/rpc-errors@^6.0.0", "@metamask/rpc-errors@^6.2.1", "@metamask/rpc-errors@^6.3.1", "@metamask/rpc-errors@^7.0.0", "@metamask/rpc-errors@^7.0.1": version "7.0.1" resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-7.0.1.tgz#0eb2231a1d5e6bb102df5ac07f365c695bf70055" From b4f9cc8ffb2a4642da2c53538481be91f87ddbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:42:06 +0000 Subject: [PATCH 02/30] yarn setup fixup --- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++++++-- ios/Podfile.lock | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 3d2ddd98edd..aefb4aa2072 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1563,7 +1563,11 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; @@ -1607,7 +1611,11 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 84b23bf4f32..0e6f34a2a2a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1202,7 +1202,7 @@ SPEC CHECKSUMS: OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Permission-BluetoothPeripheral: 34ab829f159c6cf400c57bac05f5ba1b0af7a86e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RCT-Folly: 8dc08ca5a393b48b1c523ab6220dfdcc0fe000ad + RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: fb207f74935626041e7308c9e88dcdda680f1073 RCTSearchApi: 5fc36140c598a74fd831dca924a28ed53bc7aa18 RCTTypeSafety: 146fd11361680250b7580dd1f7f601995cfad1b1 From cdd4a5883d0c859158401d495c3d76898a3f74b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:42:32 +0000 Subject: [PATCH 03/30] metro bundler: bypass lint errors --- metro.transform.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/metro.transform.js b/metro.transform.js index 3354bcd0e0f..0019c1dcaec 100644 --- a/metro.transform.js +++ b/metro.transform.js @@ -70,9 +70,9 @@ module.exports.transform = async ({ src, filename, options }) => { active: getBuildTypeFeatures(), }); - if (didModify) { - await lintTransformedFile(getESLintInstance(), filename, processedSource); - } + // if (didModify) { + // await lintTransformedFile(getESLintInstance(), filename, processedSource); + // } return defaultTransformer.transform({ src: processedSource, filename, From 178768053c9f72a2b2086fea13075aea03d10fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:43:38 +0000 Subject: [PATCH 04/30] init RemoteFeatureFlagController --- app/core/Engine.ts | 61 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index c844670d288..ae2a6a29583 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1,6 +1,17 @@ /* eslint-disable @typescript-eslint/no-shadow */ import Crypto from 'react-native-quick-crypto'; import { scrypt } from 'react-native-fast-crypto'; +import { + RemoteFeatureFlagController, + RemoteFeatureFlagControllerState, + RemoteFeatureFlagControllerGetStateAction, + ClientConfigApiService, + ClientType, + DistributionType, + EnvironmentType, + +} from '@metamask/remote-feature-flag-controller'; + import { AccountTrackerController, AccountTrackerControllerState, @@ -309,6 +320,7 @@ type GlobalActions = | NetworkControllerActions | PermissionControllerActions | SignatureControllerActions + | RemoteFeatureFlagControllerGetStateAction | LoggingControllerActions ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) | SnapsGlobalActions @@ -370,6 +382,7 @@ export interface EngineState { NetworkController: NetworkState; PreferencesController: PreferencesState; PhishingController: PhishingControllerState; + RemoteFeatureFlagController: RemoteFeatureFlagControllerState; TokenBalancesController: TokenBalancesControllerState; TokenRatesController: TokenRatesControllerState; TransactionController: TransactionControllerState; @@ -420,6 +433,7 @@ interface Controllers { PhishingController: PhishingController; PreferencesController: PreferencesController; PPOMController: PPOMController; + RemoteFeatureFlagController: RemoteFeatureFlagController; TokenBalancesController: TokenBalancesController; TokenListController: TokenListController; TokenDetectionController: TokenDetectionController; @@ -744,6 +758,29 @@ export class Engine { 'https://gas.api.cx.metamask.io/networks//suggestedGasFees', }); + const featureFlagController = new RemoteFeatureFlagController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'RemoteFeatureFlagController', + allowedActions: ['PreferencesController:getState'], + allowedEvents: ['PreferencesController:stateChange'], + }), + state: initialState.RemoteFeatureFlagController || {}, + disabled: false, // TODO: implement basic functionality toggle + clientConfigApiService: new ClientConfigApiService({ + fetch: fetchFunction, + // TODO: get env vars to config clientConfigAPI service + config: { + client: ClientType.Mobile, + environment: EnvironmentType.Production, + distribution: DistributionType.Main, + }, + }), + }); + + featureFlagController.getRemoteFeatureFlags(); + + console.log('feature flag controller', featureFlagController); + const phishingController = new PhishingController({ messenger: this.controllerMessenger.getRestricted({ name: 'PhishingController', @@ -1401,7 +1438,7 @@ export class Engine { return Boolean( hasProperty(showIncomingTransactions, currentChainId) && - showIncomingTransactions?.[currentHexChainId], + showIncomingTransactions?.[currentHexChainId], ); }, updateTransactions: true, @@ -1648,6 +1685,7 @@ export class Engine { gasFeeController, approvalController, permissionController, + featureFlagController, selectedNetworkController, new SignatureController({ messenger: this.controllerMessenger.getRestricted({ @@ -1762,7 +1800,7 @@ export class Engine { (state: NetworkState) => { if ( state.networksMetadata[state.selectedNetworkClientId].status === - NetworkStatus.Available && + NetworkStatus.Available && networkController.getNetworkClientById( networkController?.state.selectedNetworkClientId, ).configuration.chainId !== currentChainId @@ -1787,10 +1825,9 @@ export class Engine { } catch (error) { console.error( error, - `Network ID not changed, current chainId: ${ - networkController.getNetworkClientById( - networkController?.state.selectedNetworkClientId, - ).configuration.chainId + `Network ID not changed, current chainId: ${networkController.getNetworkClientById( + networkController?.state.selectedNetworkClientId, + ).configuration.chainId }`, ); } @@ -1993,7 +2030,7 @@ export class Engine { const decimalsToShow = (currentCurrency === 'usd' && 2) || undefined; if ( accountsByChainId?.[toHexadecimal(chainId)]?.[ - selectSelectedInternalAccountChecksummedAddress + selectSelectedInternalAccountChecksummedAddress ] ) { const balanceBN = hexToBN( @@ -2036,9 +2073,9 @@ export class Engine { item.balance || (item.address in tokenBalances ? renderFromTokenMinimalUnit( - tokenBalances[item.address], - item.decimals, - ) + tokenBalances[item.address], + item.decimals, + ) : undefined); const tokenBalanceFiat = balanceToFiatNumber( // TODO: Fix this by handling or eliminating the undefined case @@ -2330,6 +2367,7 @@ export default { NetworkController, PreferencesController, PhishingController, + RemoteFeatureFlagController, PPOMController, TokenBalancesController, TokenRatesController, @@ -2365,6 +2403,8 @@ export default { : CurrencyRateController.conversionRate, }; + console.log('remote feature flag state', RemoteFeatureFlagController); + return { AccountTrackerController, AddressBookController, @@ -2375,6 +2415,7 @@ export default { KeyringController, NetworkController, PhishingController, + RemoteFeatureFlagController, PPOMController, PreferencesController, TokenBalancesController, From 5299891f5a73cd1fbd1a2a71fc5bde0c696c264b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:09:34 +0000 Subject: [PATCH 05/30] controller init state destruct --- app/core/Engine.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index ae2a6a29583..e1e7ee3d203 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -764,22 +764,23 @@ export class Engine { allowedActions: ['PreferencesController:getState'], allowedEvents: ['PreferencesController:stateChange'], }), - state: initialState.RemoteFeatureFlagController || {}, + state: { + ... initialState.RemoteFeatureFlagController + }, disabled: false, // TODO: implement basic functionality toggle clientConfigApiService: new ClientConfigApiService({ fetch: fetchFunction, - // TODO: get env vars to config clientConfigAPI service config: { client: ClientType.Mobile, - environment: EnvironmentType.Production, - distribution: DistributionType.Main, + environment: EnvironmentType.Production, // TODO: get env var from .js.env. define fallback + distribution: DistributionType.Main, // TODO: get env var from .js.env. define fallback }, }), }); featureFlagController.getRemoteFeatureFlags(); - console.log('feature flag controller', featureFlagController); + console.log('feature flag controller #1#1#1', featureFlagController); const phishingController = new PhishingController({ messenger: this.controllerMessenger.getRestricted({ From 6204914cce6b5b749630e14c8b50b5cb05ec3bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:02:28 +0000 Subject: [PATCH 06/30] add feature flag selectors --- .../featureFlagController/index.test.ts | 21 ++++++++++++++ app/selectors/featureFlagController/index.ts | 18 ++++++++++++ .../minimumAppVersion/index.test.ts | 23 +++++++++++++++ .../minimumAppVersion/index.ts | 28 +++++++++++++++++++ .../minimumAppVersion/mocks.ts | 9 ++++++ .../minimumAppVersion/types.ts | 9 ++++++ app/selectors/featureFlagController/mocks.ts | 15 ++++++++++ 7 files changed, 123 insertions(+) create mode 100644 app/selectors/featureFlagController/index.test.ts create mode 100644 app/selectors/featureFlagController/index.ts create mode 100644 app/selectors/featureFlagController/minimumAppVersion/index.test.ts create mode 100644 app/selectors/featureFlagController/minimumAppVersion/index.ts create mode 100644 app/selectors/featureFlagController/minimumAppVersion/mocks.ts create mode 100644 app/selectors/featureFlagController/minimumAppVersion/types.ts create mode 100644 app/selectors/featureFlagController/mocks.ts diff --git a/app/selectors/featureFlagController/index.test.ts b/app/selectors/featureFlagController/index.test.ts new file mode 100644 index 00000000000..83f709b9d6a --- /dev/null +++ b/app/selectors/featureFlagController/index.test.ts @@ -0,0 +1,21 @@ +import { RootState } from '../../reducers'; +import mockedEngine from '../../core/__mocks__/MockedEngine'; +import { selectRemoteFeatureFlagControllerState } from '.'; +import { mockedState } from './mocks' + +jest.mock('../../core/Engine', () => ({ + init: () => mockedEngine.init(), +})); + +describe('featureFlagController', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + + it('should return feature flag initial state', () => { + const result = selectRemoteFeatureFlagControllerState(mockedState as RootState); + expect(result).toBeDefined(); + }); +}); + diff --git a/app/selectors/featureFlagController/index.ts b/app/selectors/featureFlagController/index.ts new file mode 100644 index 00000000000..5dd95f3852d --- /dev/null +++ b/app/selectors/featureFlagController/index.ts @@ -0,0 +1,18 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../../reducers'; + +export const selectRemoteFeatureFlagControllerState = (state: RootState) => + state.engine.backgroundState.RemoteFeatureFlagController; + +export const selectRemoteFeatureFlags = createSelector( + selectRemoteFeatureFlagControllerState, + (remoteFeatureFlagControllerState) => + remoteFeatureFlagControllerState.remoteFeatureFlags +); + +export const selectRemoteFeatureFlagsCacheTimestamp = createSelector( + selectRemoteFeatureFlagControllerState, + (remoteFeatureFlagControllerState) => + remoteFeatureFlagControllerState.cacheTimestamp +); + diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.test.ts b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts new file mode 100644 index 00000000000..f7a591f16bc --- /dev/null +++ b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts @@ -0,0 +1,23 @@ +import { RootState } from '../../../reducers'; +import mockedEngine from '../../../core/__mocks__/MockedEngine'; +import { mockedState } from '../mocks'; +import { + selectMobileMinimumVersions +} from '.'; + +jest.mock('../../../core/Engine', () => ({ + init: () => mockedEngine.init(), +})); + +describe('Feature flag: minimumAppVersion', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return feature flag object', () => { + const result = selectMobileMinimumVersions(mockedState as RootState); + expect(result?.appleMinimumOS).toBeDefined(); + expect(result?.appMinimumBuild).toBeDefined(); + expect(result?.androidMinimumAPIVersion).toBeDefined(); + }); +}); diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts new file mode 100644 index 00000000000..b5e1a225dab --- /dev/null +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -0,0 +1,28 @@ +import { createSelector } from 'reselect'; +import { selectRemoteFeatureFlags } from '../.'; +import { FEATURE_FLAG_NAME, FeatureFlagType } from './types'; + +export const selectMobileMinimumVersions = createSelector( + selectRemoteFeatureFlags, + (remoteFeatureFlags) => (remoteFeatureFlags.find((flag) => + flag[FEATURE_FLAG_NAME] + ) as FeatureFlagType | undefined)?.[FEATURE_FLAG_NAME] +); + +export const selectAppMinimumBuild = createSelector( + selectMobileMinimumVersions, + (mobileVersions) => + mobileVersions?.appMinimumBuild, +); + +export const selectAppleMinimumOS = createSelector( + selectMobileMinimumVersions, + (mobileVersions) => + mobileVersions?.appleMinimumOS, +); + +export const selectAndroidMinimumAPI = createSelector( + selectMobileMinimumVersions, + (mobileVersions) => + mobileVersions?.androidMinimumAPIVersion, +); diff --git a/app/selectors/featureFlagController/minimumAppVersion/mocks.ts b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts new file mode 100644 index 00000000000..bad7437c60e --- /dev/null +++ b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts @@ -0,0 +1,9 @@ +import { FEATURE_FLAG_NAME, FeatureFlagType } from "./types"; + +export const mockedMinimumAppVersion: FeatureFlagType = { + [FEATURE_FLAG_NAME]: { + appMinimumBuild: 1025, + androidMinimumAPIVersion: 29, + appleMinimumOS: 12, + }, +}; diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts new file mode 100644 index 00000000000..a10b1d65f04 --- /dev/null +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -0,0 +1,9 @@ +export const FEATURE_FLAG_NAME = 'mobileMinimumVersion'; + +export type FeatureFlagType = { + [FEATURE_FLAG_NAME]: { + appMinimumBuild: number; + appleMinimumOS: number; + androidMinimumAPIVersion: number; + }, +}; diff --git a/app/selectors/featureFlagController/mocks.ts b/app/selectors/featureFlagController/mocks.ts new file mode 100644 index 00000000000..826a55a8fa0 --- /dev/null +++ b/app/selectors/featureFlagController/mocks.ts @@ -0,0 +1,15 @@ +import { FeatureFlags } from "@metamask/remote-feature-flag-controller"; +import { mockedMinimumAppVersion } from "./minimumAppVersion/mocks"; + +export const mockedState = { + engine: { + backgroundState: { + RemoteFeatureFlagController: { + remoteFeatureFlags: [ + mockedMinimumAppVersion + ] as FeatureFlags, + cacheTimestamp: 0, + }, + }, + }, +}; From 6f8d20878b281dc53a3bd37078197aedb741ca62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:06:58 +0000 Subject: [PATCH 07/30] ff(mobileMinimumVersion): set fallback values --- .../MinimumVersions/useMinimumVersions.tsx | 10 +++---- .../minimumAppVersion/index.test.ts | 24 +++++++++++---- .../minimumAppVersion/index.ts | 30 +++++++++++++++---- .../minimumAppVersion/mocks.ts | 2 +- .../minimumAppVersion/types.ts | 2 +- app/selectors/featureFlagController/mocks.ts | 11 +++++++ 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx index 88831f75112..4c3eb87122a 100644 --- a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx +++ b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx @@ -4,22 +4,20 @@ import { createUpdateNeededNavDetails } from '../../UI/UpdateNeeded/UpdateNeeded import { useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { InteractionManager } from 'react-native'; -import { FeatureFlagsState } from '../../../core/redux/slices/featureFlags'; import { SecurityState } from '../../../../app/reducers/security'; import { RootState } from '../../../../app/reducers'; +import { selectAppMinimumBuild } from 'app/selectors/featureFlagController/minimumAppVersion'; const useMinimumVersions = () => { const { automaticSecurityChecksEnabled }: SecurityState = useSelector( (state: RootState) => state.security, ); - const { featureFlags }: FeatureFlagsState = useSelector( - (state: RootState) => state.featureFlags, - ); + + const appMinimumBuild = useSelector((state: RootState) => selectAppMinimumBuild(state)); const currentBuildNumber = Number(getBuildNumber()); const navigation = useNavigation(); const shouldTriggerUpdateFlow = - automaticSecurityChecksEnabled && - featureFlags?.mobileMinimumVersions?.appMinimumBuild > currentBuildNumber; + automaticSecurityChecksEnabled && appMinimumBuild > currentBuildNumber; useEffect(() => { if (shouldTriggerUpdateFlow) { diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.test.ts b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts index f7a591f16bc..7ffaafdfea4 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.test.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts @@ -1,6 +1,6 @@ import { RootState } from '../../../reducers'; import mockedEngine from '../../../core/__mocks__/MockedEngine'; -import { mockedState } from '../mocks'; +import { mockedState, mockedEmptyFlagsState } from '../mocks'; import { selectMobileMinimumVersions } from '.'; @@ -15,9 +15,23 @@ describe('Feature flag: minimumAppVersion', () => { }); it('should return feature flag object', () => { - const result = selectMobileMinimumVersions(mockedState as RootState); - expect(result?.appleMinimumOS).toBeDefined(); - expect(result?.appMinimumBuild).toBeDefined(); - expect(result?.androidMinimumAPIVersion).toBeDefined(); + const { + appMinimumBuild, + appleMinimumOS, + androidMinimumAPIVersion, + } = selectMobileMinimumVersions(mockedState as RootState); + expect(appleMinimumOS).toBeDefined(); + expect(appMinimumBuild).toBeDefined(); + expect(androidMinimumAPIVersion).toBeDefined(); + }); + it('should return fallback values', () => { + const { + appMinimumBuild, + appleMinimumOS, + androidMinimumAPIVersion, + } = selectMobileMinimumVersions(mockedEmptyFlagsState as RootState); + expect(appMinimumBuild).toBe(1024); + expect(appleMinimumOS).toBe(1025); + expect(androidMinimumAPIVersion).toBe(1026); }); }); diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index b5e1a225dab..d069dc1f9fb 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -2,27 +2,45 @@ import { createSelector } from 'reselect'; import { selectRemoteFeatureFlags } from '../.'; import { FEATURE_FLAG_NAME, FeatureFlagType } from './types'; +const featureFlagFallback: FeatureFlagType = { + [FEATURE_FLAG_NAME]: { + appMinimumBuild: 1024, + appleMinimumOS: 1025, + androidMinimumAPIVersion: 1026, + }, +}; + export const selectMobileMinimumVersions = createSelector( selectRemoteFeatureFlags, - (remoteFeatureFlags) => (remoteFeatureFlags.find((flag) => - flag[FEATURE_FLAG_NAME] - ) as FeatureFlagType | undefined)?.[FEATURE_FLAG_NAME] + (remoteFeatureFlags) => { + const remoteFeatureFlag = remoteFeatureFlags.find( + (remoteFeatureFlag) => + remoteFeatureFlag[FEATURE_FLAG_NAME] + ); + return ( + remoteFeatureFlag as FeatureFlagType + ?? featureFlagFallback + )[FEATURE_FLAG_NAME]; + } ); export const selectAppMinimumBuild = createSelector( selectMobileMinimumVersions, (mobileVersions) => - mobileVersions?.appMinimumBuild, + mobileVersions?.appMinimumBuild + ?? featureFlagFallback[FEATURE_FLAG_NAME].appMinimumBuild, ); export const selectAppleMinimumOS = createSelector( selectMobileMinimumVersions, (mobileVersions) => - mobileVersions?.appleMinimumOS, + mobileVersions?.appleMinimumOS + ?? featureFlagFallback[FEATURE_FLAG_NAME].appleMinimumOS, ); export const selectAndroidMinimumAPI = createSelector( selectMobileMinimumVersions, (mobileVersions) => - mobileVersions?.androidMinimumAPIVersion, + mobileVersions?.androidMinimumAPIVersion + ?? featureFlagFallback[FEATURE_FLAG_NAME].androidMinimumAPIVersion, ); diff --git a/app/selectors/featureFlagController/minimumAppVersion/mocks.ts b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts index bad7437c60e..ea6e0adb685 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/mocks.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts @@ -5,5 +5,5 @@ export const mockedMinimumAppVersion: FeatureFlagType = { appMinimumBuild: 1025, androidMinimumAPIVersion: 29, appleMinimumOS: 12, - }, + } }; diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index a10b1d65f04..40b3329f99f 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -5,5 +5,5 @@ export type FeatureFlagType = { appMinimumBuild: number; appleMinimumOS: number; androidMinimumAPIVersion: number; - }, + } }; diff --git a/app/selectors/featureFlagController/mocks.ts b/app/selectors/featureFlagController/mocks.ts index 826a55a8fa0..9a91e3d578f 100644 --- a/app/selectors/featureFlagController/mocks.ts +++ b/app/selectors/featureFlagController/mocks.ts @@ -13,3 +13,14 @@ export const mockedState = { }, }, }; + +export const mockedEmptyFlagsState = { + engine: { + backgroundState: { + RemoteFeatureFlagController: { + remoteFeatureFlags: [] as FeatureFlags, + cacheTimestamp: 0, + }, + }, + }, +}; From 608f05bb6046df4f96c927fbfd308f9939897502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:13:45 +0000 Subject: [PATCH 08/30] remove feature flag saga / slice --- .../redux/slices/featureFlags/index.test.ts | 51 ------------ app/core/redux/slices/featureFlags/index.ts | 77 ------------------- app/store/sagas/index.ts | 42 ---------- 3 files changed, 170 deletions(-) delete mode 100644 app/core/redux/slices/featureFlags/index.test.ts delete mode 100644 app/core/redux/slices/featureFlags/index.ts diff --git a/app/core/redux/slices/featureFlags/index.test.ts b/app/core/redux/slices/featureFlags/index.test.ts deleted file mode 100644 index 3915e1853ba..00000000000 --- a/app/core/redux/slices/featureFlags/index.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import reducer, { - getFeatureFlags, - getFeatureFlagsSuccess, - getFeatureFlagsError, - initialState, -} from './index'; - -describe('featureFlags slice', () => { - it('should handle initial state', () => { - expect(reducer(undefined, { type: 'unknown' })).toEqual(initialState); - }); - - it('should handle getFeatureFlags', () => { - const nextState = reducer(initialState, getFeatureFlags()); - expect(nextState).toEqual({ - ...initialState, - loading: true, - error: null, - }); - }); - - it('should handle getFeatureFlagsSuccess', () => { - const featureFlags = { - mobileMinimumVersions: { - appMinimumBuild: 1243, - appleMinimumOS: 6, - androidMinimumAPIVersion: 21, - }, - }; - const nextState = reducer( - initialState, - getFeatureFlagsSuccess(featureFlags), - ); - expect(nextState).toEqual({ - ...initialState, - featureFlags, - loading: false, - error: null, - }); - }); - - it('should handle getFeatureFlagsError', () => { - const error = 'Failed to fetch feature flags'; - const nextState = reducer(initialState, getFeatureFlagsError(error)); - expect(nextState).toEqual({ - ...initialState, - loading: false, - error, - }); - }); -}); diff --git a/app/core/redux/slices/featureFlags/index.ts b/app/core/redux/slices/featureFlags/index.ts deleted file mode 100644 index 2e27d4e9bb2..00000000000 --- a/app/core/redux/slices/featureFlags/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; - -export interface FeatureFlagsState { - featureFlags: { - mobileMinimumVersions: { - appMinimumBuild: number; - appleMinimumOS: number; - androidMinimumAPIVersion: number; - }; - }; - loading: boolean; - error: string | null; -} - -export const initialState: FeatureFlagsState = { - featureFlags: { - mobileMinimumVersions: { - appMinimumBuild: 1243, - appleMinimumOS: 6, - androidMinimumAPIVersion: 21, - }, - }, - loading: false, - error: null, -}; - -const name = 'featureFlags'; - -const slice = createSlice({ - name, - initialState, - reducers: { - /** - * Initiates the fetching of feature flags. - * @param state - The current state of the featureFlags slice. - */ - getFeatureFlags: (state: FeatureFlagsState) => { - state.loading = true; - state.error = null; - }, - /** - * Handles the successful fetching of feature flags. - * @param state - The current state of the featureFlags slice. - * @param action - An action with the fetched feature flags as payload. - */ - getFeatureFlagsSuccess: ( - state: FeatureFlagsState, - action: PayloadAction, - ) => { - state.featureFlags = action.payload; - state.loading = false; - state.error = null; - }, - /** - * Handles errors that occur during the fetching of feature flags. - * @param state - The current state of the featureFlags slice. - * @param action - An action with the error message as payload. - */ - getFeatureFlagsError: ( - state: FeatureFlagsState, - action: PayloadAction, - ) => { - state.loading = false; - state.error = action.payload; - }, - }, -}); - -const { actions, reducer } = slice; - -export default reducer; - -export const { getFeatureFlags, getFeatureFlagsSuccess, getFeatureFlagsError } = - actions; - -export const FETCH_FEATURE_FLAGS = 'getFeatureFlags'; -export type FETCH_FEATURE_FLAGS = typeof FETCH_FEATURE_FLAGS; diff --git a/app/store/sagas/index.ts b/app/store/sagas/index.ts index 0985f917d33..32afb2b55c7 100644 --- a/app/store/sagas/index.ts +++ b/app/store/sagas/index.ts @@ -19,14 +19,6 @@ import { restoreXMLHttpRequest, } from './xmlHttpRequestOverride'; -import { - getFeatureFlagsSuccess, - getFeatureFlagsError, - FeatureFlagsState, -} from '../../../app/core/redux/slices/featureFlags'; - -import launchDarklyURL from '../../../app/util/featureFlags'; - export function* appLockStateMachine() { let biometricsListenerTask: Task | undefined; while (true) { @@ -132,42 +124,8 @@ export function* basicFunctionalityToggle() { } } -function arrayToObject(data: []): FeatureFlagsState['featureFlags'] { - return data.reduce((obj, current) => { - Object.assign(obj, current); - return obj; - }, {} as FeatureFlagsState['featureFlags']); -} - -function* fetchFeatureFlags(): Generator { - try { - const response: Response = (yield fetch( - launchDarklyURL( - process.env.METAMASK_BUILD_TYPE, - process.env.METAMASK_ENVIRONMENT, - ), - )) as Response; - const jsonData = (yield response.json()) as { message: string } | []; - - if (!response.ok) { - if (jsonData && typeof jsonData === 'object' && 'message' in jsonData) { - yield put(getFeatureFlagsError(jsonData.message)); - } else { - yield put(getFeatureFlagsError('Unknown error')); - } - return; - } - - yield put(getFeatureFlagsSuccess(arrayToObject(jsonData as []))); - } catch (error) { - Logger.log(error); - yield put(getFeatureFlagsError(error as string)); - } -} - // Main generator function that initializes other sagas in parallel. export function* rootSaga() { yield fork(authStateMachine); yield fork(basicFunctionalityToggle); - yield fork(fetchFeatureFlags); } From a8ec092928cf22de59226eb63a190f8063a724d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:18:51 +0000 Subject: [PATCH 09/30] remote feature flags behing basic func toggle --- app/core/Engine.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index e1e7ee3d203..55e8bafb8ff 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -765,9 +765,10 @@ export class Engine { allowedEvents: ['PreferencesController:stateChange'], }), state: { - ... initialState.RemoteFeatureFlagController + ...initialState.RemoteFeatureFlagController }, - disabled: false, // TODO: implement basic functionality toggle + disabled: + store.getState().settings.basicFunctionalityEnabled === false, clientConfigApiService: new ClientConfigApiService({ fetch: fetchFunction, config: { From 01df041d5d398e3f6a82965d5bc35733f329edb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:10:19 +0000 Subject: [PATCH 10/30] fix controller state change event --- .../hooks/MinimumVersions/useMinimumVersions.tsx | 2 +- app/core/Engine.ts | 8 ++------ app/core/EngineService/EngineService.ts | 4 ++++ app/reducers/index.ts | 5 ----- .../featureFlagController/minimumAppVersion/index.ts | 1 + .../featureFlagController/minimumAppVersion/types.ts | 2 +- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx index 4c3eb87122a..26ffa9e9101 100644 --- a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx +++ b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx @@ -6,7 +6,7 @@ import { useNavigation } from '@react-navigation/native'; import { InteractionManager } from 'react-native'; import { SecurityState } from '../../../../app/reducers/security'; import { RootState } from '../../../../app/reducers'; -import { selectAppMinimumBuild } from 'app/selectors/featureFlagController/minimumAppVersion'; +import { selectAppMinimumBuild } from '../../../../app/selectors/featureFlagController/minimumAppVersion'; const useMinimumVersions = () => { const { automaticSecurityChecksEnabled }: SecurityState = useSelector( diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 55e8bafb8ff..2f385d6b86a 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -761,8 +761,8 @@ export class Engine { const featureFlagController = new RemoteFeatureFlagController({ messenger: this.controllerMessenger.getRestricted({ name: 'RemoteFeatureFlagController', - allowedActions: ['PreferencesController:getState'], - allowedEvents: ['PreferencesController:stateChange'], + allowedActions: ['RemoteFeatureFlagController:getState'], + allowedEvents: ['RemoteFeatureFlagController:stateChange'], }), state: { ...initialState.RemoteFeatureFlagController @@ -781,8 +781,6 @@ export class Engine { featureFlagController.getRemoteFeatureFlags(); - console.log('feature flag controller #1#1#1', featureFlagController); - const phishingController = new PhishingController({ messenger: this.controllerMessenger.getRestricted({ name: 'PhishingController', @@ -2405,8 +2403,6 @@ export default { : CurrencyRateController.conversionRate, }; - console.log('remote feature flag state', RemoteFeatureFlagController); - return { AccountTrackerController, AddressBookController, diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index ecbec9b8715..e47ffe5261c 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -96,6 +96,10 @@ class EngineService { name: 'PreferencesController', key: `${engine.context.PreferencesController.name}:stateChange`, }, + { + name: 'RemoteFeatureFlagController', + key: `${engine.context.RemoteFeatureFlagController.name}:stateChange`, + }, { name: 'SelectedNetworkController', key: `${engine.context.SelectedNetworkController.name}:stateChange`, diff --git a/app/reducers/index.ts b/app/reducers/index.ts index 39f5f91c8c8..6e409d4f23d 100644 --- a/app/reducers/index.ts +++ b/app/reducers/index.ts @@ -1,9 +1,6 @@ import bookmarksReducer from './bookmarks'; import browserReducer from './browser'; import engineReducer from '../core/redux/slices/engine'; -import featureFlagsReducer, { - FeatureFlagsState, -} from '../core/redux/slices/featureFlags'; import privacyReducer from './privacy'; import modalsReducer from './modals'; import settingsReducer from './settings'; @@ -57,7 +54,6 @@ export interface RootState { // eslint-disable-next-line @typescript-eslint/no-explicit-any collectibles: any; engine: { backgroundState: EngineState }; - featureFlags: FeatureFlagsState; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any privacy: any; @@ -135,7 +131,6 @@ const rootReducer = combineReducers({ // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any engine: engineReducer as any, - featureFlags: featureFlagsReducer, privacy: privacyReducer, bookmarks: bookmarksReducer, browser: browserReducer, diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index d069dc1f9fb..43af37417f2 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -17,6 +17,7 @@ export const selectMobileMinimumVersions = createSelector( (remoteFeatureFlag) => remoteFeatureFlag[FEATURE_FLAG_NAME] ); + return ( remoteFeatureFlag as FeatureFlagType ?? featureFlagFallback diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index 40b3329f99f..3f7abdf0cfa 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -1,4 +1,4 @@ -export const FEATURE_FLAG_NAME = 'mobileMinimumVersion'; +export const FEATURE_FLAG_NAME = 'mobileMinimumVersions'; export type FeatureFlagType = { [FEATURE_FLAG_NAME]: { From c8c2af05b267a25097dd99bedea05b79badf597c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:18:21 +0000 Subject: [PATCH 11/30] get env vars to init feature flag controllers --- app/core/Engine.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 2f385d6b86a..9c287e7c549 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -281,6 +281,7 @@ import { trace } from '../util/trace'; import { MetricsEventBuilder } from './Analytics/MetricsEventBuilder'; import { JsonMap } from './Analytics/MetaMetrics.types'; import { isPooledStakingFeatureEnabled } from '../components/UI/Stake/constants'; +import { RemoteFeatureFlagControllerActions } from '@metamask/remote-feature-flag-controller/dist/remote-feature-flag-controller.cjs'; const NON_EMPTY = 'NON_EMPTY'; @@ -758,11 +759,30 @@ export class Engine { 'https://gas.api.cx.metamask.io/networks//suggestedGasFees', }); + const getFeatureFlagAppEnvironment = () => { + const env = process.env.METAMASK_ENVIRONMENT; + switch(env) { + case 'local': return EnvironmentType.Development; + case 'pre-release': return EnvironmentType.ReleaseCandidate; + case 'production': return EnvironmentType.Production; + default: return EnvironmentType.Development; + } + }; + const getFeatureFlagAppDistribution = () => { + const dist = process.env.METAMASK_BUILD_TYPE; + switch(dist) { + case 'main': return DistributionType.Main; + case 'flask': return DistributionType.Flask; + default: return DistributionType.Main; + } + + }; + const featureFlagController = new RemoteFeatureFlagController({ messenger: this.controllerMessenger.getRestricted({ name: 'RemoteFeatureFlagController', - allowedActions: ['RemoteFeatureFlagController:getState'], - allowedEvents: ['RemoteFeatureFlagController:stateChange'], + allowedActions: ['PreferencesController:getState'], + allowedEvents: ['PreferencesController:stateChange'], }), state: { ...initialState.RemoteFeatureFlagController @@ -773,8 +793,8 @@ export class Engine { fetch: fetchFunction, config: { client: ClientType.Mobile, - environment: EnvironmentType.Production, // TODO: get env var from .js.env. define fallback - distribution: DistributionType.Main, // TODO: get env var from .js.env. define fallback + environment: getFeatureFlagAppEnvironment(), + distribution: getFeatureFlagAppDistribution(), }, }), }); From ca330065065eea972ad855ea6c8de2052c9fd7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:23:52 +0000 Subject: [PATCH 12/30] update min version fallback values --- .../featureFlagController/minimumAppVersion/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index 43af37417f2..40f4bb1c4c6 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -4,9 +4,9 @@ import { FEATURE_FLAG_NAME, FeatureFlagType } from './types'; const featureFlagFallback: FeatureFlagType = { [FEATURE_FLAG_NAME]: { - appMinimumBuild: 1024, - appleMinimumOS: 1025, - androidMinimumAPIVersion: 1026, + appMinimumBuild: 1243, + appleMinimumOS: 6, + androidMinimumAPIVersion: 21, }, }; From 27b32a3bf00c8c429c1532cf063a441eb82e9074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:32:17 +0000 Subject: [PATCH 13/30] remove controller messenger allowances --- app/core/Engine.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 9c287e7c549..5afef0be5a0 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -781,8 +781,8 @@ export class Engine { const featureFlagController = new RemoteFeatureFlagController({ messenger: this.controllerMessenger.getRestricted({ name: 'RemoteFeatureFlagController', - allowedActions: ['PreferencesController:getState'], - allowedEvents: ['PreferencesController:stateChange'], + allowedActions: [], + allowedEvents: [], }), state: { ...initialState.RemoteFeatureFlagController From d7aed0d78ea01cdc726d96a55993f89868da5d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:36:32 +0000 Subject: [PATCH 14/30] Revert "metro bundler: bypass lint errors" This reverts commit cdd4a5883d0c859158401d495c3d76898a3f74b5. --- metro.transform.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/metro.transform.js b/metro.transform.js index 0019c1dcaec..3354bcd0e0f 100644 --- a/metro.transform.js +++ b/metro.transform.js @@ -70,9 +70,9 @@ module.exports.transform = async ({ src, filename, options }) => { active: getBuildTypeFeatures(), }); - // if (didModify) { - // await lintTransformedFile(getESLintInstance(), filename, processedSource); - // } + if (didModify) { + await lintTransformedFile(getESLintInstance(), filename, processedSource); + } return defaultTransformer.transform({ src: processedSource, filename, From b80145dbd889d95ff6f5aebcddda0ce715c10fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:39:03 +0000 Subject: [PATCH 15/30] remove unused import --- app/core/Engine.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 5afef0be5a0..0f3999baff4 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -281,7 +281,6 @@ import { trace } from '../util/trace'; import { MetricsEventBuilder } from './Analytics/MetricsEventBuilder'; import { JsonMap } from './Analytics/MetaMetrics.types'; import { isPooledStakingFeatureEnabled } from '../components/UI/Stake/constants'; -import { RemoteFeatureFlagControllerActions } from '@metamask/remote-feature-flag-controller/dist/remote-feature-flag-controller.cjs'; const NON_EMPTY = 'NON_EMPTY'; From 9a67929f2bee7e03deb0a52ca9dad560afa56a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:43:24 +0000 Subject: [PATCH 16/30] fix lint errors --- .../featureFlagController/minimumAppVersion/index.ts | 4 ++-- .../featureFlagController/minimumAppVersion/mocks.ts | 2 +- .../featureFlagController/minimumAppVersion/types.ts | 2 +- app/selectors/featureFlagController/mocks.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index 40f4bb1c4c6..d685b484c5c 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -14,8 +14,8 @@ export const selectMobileMinimumVersions = createSelector( selectRemoteFeatureFlags, (remoteFeatureFlags) => { const remoteFeatureFlag = remoteFeatureFlags.find( - (remoteFeatureFlag) => - remoteFeatureFlag[FEATURE_FLAG_NAME] + (featureflag) => + featureflag[FEATURE_FLAG_NAME] ); return ( diff --git a/app/selectors/featureFlagController/minimumAppVersion/mocks.ts b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts index ea6e0adb685..b029c752a94 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/mocks.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/mocks.ts @@ -1,4 +1,4 @@ -import { FEATURE_FLAG_NAME, FeatureFlagType } from "./types"; +import { FEATURE_FLAG_NAME, FeatureFlagType } from './types'; export const mockedMinimumAppVersion: FeatureFlagType = { [FEATURE_FLAG_NAME]: { diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index 3f7abdf0cfa..d5a40f360b0 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -1,6 +1,6 @@ export const FEATURE_FLAG_NAME = 'mobileMinimumVersions'; -export type FeatureFlagType = { +export interface FeatureFlagType { [FEATURE_FLAG_NAME]: { appMinimumBuild: number; appleMinimumOS: number; diff --git a/app/selectors/featureFlagController/mocks.ts b/app/selectors/featureFlagController/mocks.ts index 9a91e3d578f..8672e38fb79 100644 --- a/app/selectors/featureFlagController/mocks.ts +++ b/app/selectors/featureFlagController/mocks.ts @@ -1,5 +1,5 @@ -import { FeatureFlags } from "@metamask/remote-feature-flag-controller"; -import { mockedMinimumAppVersion } from "./minimumAppVersion/mocks"; +import { FeatureFlags } from '@metamask/remote-feature-flag-controller'; +import { mockedMinimumAppVersion } from './minimumAppVersion/mocks'; export const mockedState = { engine: { @@ -7,7 +7,7 @@ export const mockedState = { RemoteFeatureFlagController: { remoteFeatureFlags: [ mockedMinimumAppVersion - ] as FeatureFlags, + ], cacheTimestamp: 0, }, }, From 934a13ab057f82d399906cae996a41c33a63ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:46:35 +0000 Subject: [PATCH 17/30] remove unneded semicolon --- app/selectors/featureFlagController/minimumAppVersion/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index d5a40f360b0..99f9d7fdbec 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -6,4 +6,5 @@ export interface FeatureFlagType { appleMinimumOS: number; androidMinimumAPIVersion: number; } -}; +} + From faa6cb0fce3095c947881376453f7b6079041631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:47:02 +0000 Subject: [PATCH 18/30] remove featureFlag initial root state --- app/util/test/initial-root-state.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/util/test/initial-root-state.ts b/app/util/test/initial-root-state.ts index 58be6fea8d8..69e95dbf5c9 100644 --- a/app/util/test/initial-root-state.ts +++ b/app/util/test/initial-root-state.ts @@ -5,7 +5,6 @@ import { initialState as initialSecurityState } from '../../reducers/security'; import { initialState as initialInpageProvider } from '../../core/redux/slices/inpageProvider'; import { initialState as transactionMetrics } from '../../core/redux/slices/transactionMetrics'; import { initialState as originThrottling } from '../../core/redux/slices/originThrottling'; -import { initialState as initialFeatureFlagsState } from '../../core/redux/slices/featureFlags'; import initialBackgroundState from './initial-background-state.json'; import { userInitialState } from '../../reducers/user'; @@ -21,7 +20,6 @@ const initialRootState: RootState = { privacy: undefined, bookmarks: undefined, browser: undefined, - featureFlags: initialFeatureFlagsState, modals: undefined, settings: undefined, alert: undefined, From d22d1cb2afee184af9b224da6750c076aab12815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:24:32 +0000 Subject: [PATCH 19/30] Engine: split Feature flag controller --- app/core/Engine/Engine.ts | 67 ++++--------------- .../RemoteFeatureFlagController/index.ts | 47 +++++++++++++ .../RemoteFeatureFlagController/types.ts | 9 +++ .../RemoteFeatureFlagController/utils.ts | 24 +++++++ 4 files changed, 94 insertions(+), 53 deletions(-) create mode 100644 app/core/Engine/controllers/RemoteFeatureFlagController/index.ts create mode 100644 app/core/Engine/controllers/RemoteFeatureFlagController/types.ts create mode 100644 app/core/Engine/controllers/RemoteFeatureFlagController/utils.ts diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index c48533e3883..3c9f3f832aa 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -1,13 +1,6 @@ /* eslint-disable @typescript-eslint/no-shadow */ import Crypto from 'react-native-quick-crypto'; import { scrypt } from 'react-native-fast-crypto'; -import { - RemoteFeatureFlagController, - ClientConfigApiService, - ClientType, - DistributionType, - EnvironmentType, -} from '@metamask/remote-feature-flag-controller'; import { AccountTrackerController, @@ -87,6 +80,7 @@ import { LedgerMobileBridge, LedgerTransportMiddleware, } from '@metamask/eth-ledger-bridge-keyring'; +import RemoteFeatureFlagController from './controllers/RemoteFeatureFlagController'; import { Encryptor, LEGACY_DERIVATION_OPTIONS } from '../Encryptor'; import { isMainnetByChainId, @@ -274,6 +268,9 @@ export class Engine { ) { this.controllerMessenger = new ExtendedControllerMessenger(); + // Basic Functionality toggle defaults to true + const isBasicFunctionalityEnabled = store.getState().settings?.basicFunctionalityEnabled ?? true; + const approvalController = new ApprovalController({ messenger: this.controllerMessenger.getRestricted({ name: 'ApprovalController', @@ -486,48 +483,13 @@ export class Engine { 'https://gas.api.cx.metamask.io/networks//suggestedGasFees', }); - const getFeatureFlagAppEnvironment = () => { - const env = process.env.METAMASK_ENVIRONMENT; - switch(env) { - case 'local': return EnvironmentType.Development; - case 'pre-release': return EnvironmentType.ReleaseCandidate; - case 'production': return EnvironmentType.Production; - default: return EnvironmentType.Development; - } - }; - const getFeatureFlagAppDistribution = () => { - const dist = process.env.METAMASK_BUILD_TYPE; - switch(dist) { - case 'main': return DistributionType.Main; - case 'flask': return DistributionType.Flask; - default: return DistributionType.Main; - } - - }; - - const remoteFeatureFlagController = new RemoteFeatureFlagController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'RemoteFeatureFlagController', - allowedActions: [], - allowedEvents: [], - }), - state: { - ...initialState.RemoteFeatureFlagController - }, - disabled: - store.getState().settings.basicFunctionalityEnabled === false, - clientConfigApiService: new ClientConfigApiService({ - fetch: fetchFunction, - config: { - client: ClientType.Mobile, - environment: getFeatureFlagAppEnvironment(), - distribution: getFeatureFlagAppDistribution(), - }, - }), + const remoteFeatureFlagController = RemoteFeatureFlagController.init({ + initialState, + controllerMessenger: this.controllerMessenger, + fetchFunction, + disabled: !isBasicFunctionalityEnabled }); - remoteFeatureFlagController.getRemoteFeatureFlags(); - const phishingController = new PhishingController({ messenger: this.controllerMessenger.getRestricted({ name: 'PhishingController', @@ -983,8 +945,7 @@ export class Engine { encryptor, getMnemonic: getPrimaryKeyringMnemonic.bind(this), getFeatureFlags: () => ({ - disableSnaps: - store.getState().settings.basicFunctionalityEnabled === false, + disableSnaps: !isBasicFunctionalityEnabled, }), }); @@ -1805,7 +1766,7 @@ export class Engine { const tokenBalances = allTokenBalances?.[selectedInternalAccount.address as Hex]?.[ - chainId + chainId ] ?? {}; tokens.forEach( (item: { address: string; balance?: string; decimals: number }) => { @@ -1816,9 +1777,9 @@ export class Engine { item.balance || (item.address in tokenBalances ? renderFromTokenMinimalUnit( - tokenBalances[item.address as Hex], - item.decimals, - ) + tokenBalances[item.address as Hex], + item.decimals, + ) : undefined); const tokenBalanceFiat = balanceToFiatNumber( // TODO: Fix this by handling or eliminating the undefined case diff --git a/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts b/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts new file mode 100644 index 00000000000..b22827a3f90 --- /dev/null +++ b/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts @@ -0,0 +1,47 @@ +import { + RemoteFeatureFlagController, + ClientConfigApiService, + ClientType, +} from '@metamask/remote-feature-flag-controller'; +import { RemoteFeatureFlagInitParamTypes } from './types'; +import { + getFeatureFlagAppEnvironment, + getFeatureFlagAppDistribution +} from './utils'; + + +const init = ({ + initialState, + controllerMessenger, + fetchFunction, + disabled, +}: RemoteFeatureFlagInitParamTypes) => { + + const remoteFeatureFlagController = new RemoteFeatureFlagController({ + messenger: controllerMessenger.getRestricted({ + name: 'RemoteFeatureFlagController', + allowedActions: [], + allowedEvents: [], + }), + state: { + ...initialState.RemoteFeatureFlagController + }, + disabled, + clientConfigApiService: new ClientConfigApiService({ + fetch: fetchFunction, + config: { + client: ClientType.Mobile, + environment: getFeatureFlagAppEnvironment(), + distribution: getFeatureFlagAppDistribution(), + }, + }), + }); + + remoteFeatureFlagController.getRemoteFeatureFlags(); + + return remoteFeatureFlagController; +} + +export default { + init +} diff --git a/app/core/Engine/controllers/RemoteFeatureFlagController/types.ts b/app/core/Engine/controllers/RemoteFeatureFlagController/types.ts new file mode 100644 index 00000000000..ca167eda478 --- /dev/null +++ b/app/core/Engine/controllers/RemoteFeatureFlagController/types.ts @@ -0,0 +1,9 @@ +import { ControllerMessenger, EngineState } from '../../types'; + +export interface RemoteFeatureFlagInitParamTypes { + initialState: Partial; + controllerMessenger: ControllerMessenger, + fetchFunction: typeof fetch, + disabled: boolean +} + diff --git a/app/core/Engine/controllers/RemoteFeatureFlagController/utils.ts b/app/core/Engine/controllers/RemoteFeatureFlagController/utils.ts new file mode 100644 index 00000000000..d91ed1c0faa --- /dev/null +++ b/app/core/Engine/controllers/RemoteFeatureFlagController/utils.ts @@ -0,0 +1,24 @@ +import { + DistributionType, + EnvironmentType, +} from '@metamask/remote-feature-flag-controller'; + +export const getFeatureFlagAppEnvironment = () => { + const env = process.env.METAMASK_ENVIRONMENT; + switch (env) { + case 'local': return EnvironmentType.Development; + case 'pre-release': return EnvironmentType.ReleaseCandidate; + case 'production': return EnvironmentType.Production; + default: return EnvironmentType.Development; + } +}; + +export const getFeatureFlagAppDistribution = () => { + const dist = process.env.METAMASK_BUILD_TYPE; + switch (dist) { + case 'main': return DistributionType.Main; + case 'flask': return DistributionType.Flask; + default: return DistributionType.Main; + } +}; + From ba1802e63dfd275233b17f6ed0db2cb60cadb42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:25:22 +0000 Subject: [PATCH 20/30] set RemoteFeatureFlagController & minappversion flag codeowners --- .github/CODEOWNERS | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 16348bdf720..f7d8d322eaf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,19 +7,24 @@ app/component-library/ @MetaMask/design-system-engineers # Platform Team -.github/CODEOWNERS @MetaMask/mobile-platform -patches/ @MetaMask/mobile-platform -app/core/Engine/Engine.ts @MetaMask/mobile-platform -app/core/Engine/Engine.test.ts @MetaMask/mobile-platform -app/core/Engine/index.ts @MetaMask/mobile-platform -app/core/Engine/types.ts @MetaMask/mobile-platform -app/core/Analytics/ @MetaMask/mobile-platform -app/util/metrics/ @MetaMask/mobile-platform -app/components/hooks/useMetrics/ @MetaMask/mobile-platform -app/store/migrations/ @MetaMask/mobile-platform -bitrise.yml @MetaMask/mobile-platform -yarn.lock @MetaMask/mobile-platform -ios/Podfile.lock @MetaMask/mobile-platform +.github/CODEOWNERS @MetaMask/mobile-platform +patches/ @MetaMask/mobile-platform +app/core/Engine/Engine.ts @MetaMask/mobile-platform +app/core/Engine/Engine.test.ts @MetaMask/mobile-platform +app/core/Engine/index.ts @MetaMask/mobile-platform +app/core/Engine/types.ts @MetaMask/mobile-platform +app/core/Engine/controllers/RemoteFeatureFlagController/ @MetaMask/mobile-platform +app/core/Analytics/ @MetaMask/mobile-platform +app/util/metrics/ @MetaMask/mobile-platform +app/components/hooks/useMetrics/ @MetaMask/mobile-platform +app/selectors/featureFlagController/index.ts @MetaMask/mobile-platform +app/selectors/featureFlagController/index.test.ts @MetaMask/mobile-platform +app/selectors/featureFlagController/mocks.ts @MetaMask/mobile-platform +app/selectors/featureFlagController/minimumAppVersion/ @MetaMask/mobile-platform +app/store/migrations/ @MetaMask/mobile-platform +bitrise.yml @MetaMask/mobile-platform +yarn.lock @MetaMask/mobile-platform +ios/Podfile.lock @MetaMask/mobile-platform # Ramps Team app/components/UI/Ramp/ @MetaMask/ramp @@ -53,10 +58,10 @@ app/components/UI/Swaps @MetaMask/swaps-engineers app/components/Views/Notifications @MetaMask/notifications app/components/Views/Settings/NotificationsSettings @MetaMask/notifications app/components/UI/Notifications @MetaMask/notifications -app/reducers/notification @MetaMask/notifications -app/actions/notification @MetaMask/notifications -app/selectors/notification @MetaMask/notifications -app/util/notifications @MetaMask/notifications +app/reducers/notification @MetaMask/notifications +app/actions/notification @MetaMask/notifications +app/selectors/notification @MetaMask/notifications +app/util/notifications @MetaMask/notifications app/store/util/notifications @MetaMask/notifications # LavaMoat Team From 5e97075e5559705ddf65f0dc9378aa119dd62f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:26:15 +0000 Subject: [PATCH 21/30] minimumAppVersion featureflag: improve types --- .../minimumAppVersion/index.ts | 15 ++++++++++----- .../minimumAppVersion/types.ts | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index d685b484c5c..75e2ddd361c 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -1,6 +1,10 @@ import { createSelector } from 'reselect'; import { selectRemoteFeatureFlags } from '../.'; -import { FEATURE_FLAG_NAME, FeatureFlagType } from './types'; +import { + FEATURE_FLAG_NAME, + FeatureFlagType, + UndefinedFeatureFlagType, +} from './types'; const featureFlagFallback: FeatureFlagType = { [FEATURE_FLAG_NAME]: { @@ -18,10 +22,11 @@ export const selectMobileMinimumVersions = createSelector( featureflag[FEATURE_FLAG_NAME] ); - return ( - remoteFeatureFlag as FeatureFlagType - ?? featureFlagFallback - )[FEATURE_FLAG_NAME]; + const featureFlagValues = + remoteFeatureFlag as UndefinedFeatureFlagType + ?? featureFlagFallback; + + return featureFlagValues[FEATURE_FLAG_NAME]; } ); diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index 99f9d7fdbec..0368ba74095 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -8,3 +8,4 @@ export interface FeatureFlagType { } } +export type UndefinedFeatureFlagType = FeatureFlagType | undefined From c838b21ce46f19a19d2f41f56036e7ed02403699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:29:20 +0000 Subject: [PATCH 22/30] remove dead code --- app/store/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/store/index.ts b/app/store/index.ts index 01200a0261f..5f74eb552d9 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -87,11 +87,6 @@ const createStoreAndPersistor = async () => { basicFunctionalityEnabled: store.getState().settings.basicFunctionalityEnabled, }); - // Fetch feature flags only if basic functionality is enabled - store.getState().settings.basicFunctionalityEnabled && - store.dispatch({ - type: 'FETCH_FEATURE_FLAGS', - }); EngineService.initalizeEngine(store); From 426655eda02f040660f00f312b05330c09c84618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:54:34 +0000 Subject: [PATCH 23/30] add Feature flag controller logger --- .../controllers/RemoteFeatureFlagController/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts b/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts index b22827a3f90..6f097f1fede 100644 --- a/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts +++ b/app/core/Engine/controllers/RemoteFeatureFlagController/index.ts @@ -3,6 +3,7 @@ import { ClientConfigApiService, ClientType, } from '@metamask/remote-feature-flag-controller'; +import Logger from '../../../../util/Logger'; import { RemoteFeatureFlagInitParamTypes } from './types'; import { getFeatureFlagAppEnvironment, @@ -37,7 +38,13 @@ const init = ({ }), }); - remoteFeatureFlagController.getRemoteFeatureFlags(); + if (disabled) { + Logger.log('Feature Flags Controller disabled'); + } else { + remoteFeatureFlagController.getRemoteFeatureFlags().then((featFlags) => { + Logger.log(`Received feature flags ${JSON.stringify(featFlags)}`); + }); + } return remoteFeatureFlagController; } From 8014b459fd54c87877eb0120415bb6f65aba8e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:52:10 +0000 Subject: [PATCH 24/30] fix basic functionality toggle --- app/core/Engine/Engine.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 3c9f3f832aa..c2f4960d055 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -269,7 +269,8 @@ export class Engine { this.controllerMessenger = new ExtendedControllerMessenger(); // Basic Functionality toggle defaults to true - const isBasicFunctionalityEnabled = store.getState().settings?.basicFunctionalityEnabled ?? true; + const getBasicFunctionalityToggleState = () => + store.getState().settings?.basicFunctionalityEnabled ?? true; const approvalController = new ApprovalController({ messenger: this.controllerMessenger.getRestricted({ @@ -487,7 +488,7 @@ export class Engine { initialState, controllerMessenger: this.controllerMessenger, fetchFunction, - disabled: !isBasicFunctionalityEnabled + disabled: getBasicFunctionalityToggleState() === false }); const phishingController = new PhishingController({ @@ -945,7 +946,7 @@ export class Engine { encryptor, getMnemonic: getPrimaryKeyringMnemonic.bind(this), getFeatureFlags: () => ({ - disableSnaps: !isBasicFunctionalityEnabled, + disableSnaps: getBasicFunctionalityToggleState() === false, }), }); From 9a35568b23376a6c80e835a113e91a50b7a8a224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:01:29 +0000 Subject: [PATCH 25/30] Revert "yarn setup fixup" This reverts commit b4f9cc8ffb2a4642da2c53538481be91f87ddbd2. --- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++---------- ios/Podfile.lock | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 844f796ddda..6515458244b 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1563,11 +1563,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; @@ -1611,11 +1607,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0e6f34a2a2a..84b23bf4f32 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1202,7 +1202,7 @@ SPEC CHECKSUMS: OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Permission-BluetoothPeripheral: 34ab829f159c6cf400c57bac05f5ba1b0af7a86e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 8dc08ca5a393b48b1c523ab6220dfdcc0fe000ad RCTRequired: fb207f74935626041e7308c9e88dcdda680f1073 RCTSearchApi: 5fc36140c598a74fd831dca924a28ed53bc7aa18 RCTTypeSafety: 146fd11361680250b7580dd1f7f601995cfad1b1 From 8c305c1d190cc8071452747af4fa216ae9806458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:17:44 +0000 Subject: [PATCH 26/30] improve minimumAppVersion TS types --- app/selectors/featureFlagController/minimumAppVersion/types.ts | 2 +- app/selectors/featureFlagController/mocks.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/selectors/featureFlagController/minimumAppVersion/types.ts b/app/selectors/featureFlagController/minimumAppVersion/types.ts index 0368ba74095..babfb729ad9 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/types.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/types.ts @@ -1,7 +1,7 @@ export const FEATURE_FLAG_NAME = 'mobileMinimumVersions'; export interface FeatureFlagType { - [FEATURE_FLAG_NAME]: { + [featureFlagName: string]: { appMinimumBuild: number; appleMinimumOS: number; androidMinimumAPIVersion: number; diff --git a/app/selectors/featureFlagController/mocks.ts b/app/selectors/featureFlagController/mocks.ts index 8672e38fb79..84c97da5d03 100644 --- a/app/selectors/featureFlagController/mocks.ts +++ b/app/selectors/featureFlagController/mocks.ts @@ -7,7 +7,7 @@ export const mockedState = { RemoteFeatureFlagController: { remoteFeatureFlags: [ mockedMinimumAppVersion - ], + ] as FeatureFlags, cacheTimestamp: 0, }, }, From c8495e55f2ab7e2e93df30c229ab3c2531564b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:20:11 +0000 Subject: [PATCH 27/30] fix unit tests --- app/core/Engine/Engine.test.ts | 1 + app/core/EngineService/EngineService.test.ts | 1 + .../featureFlagController/minimumAppVersion/index.test.ts | 8 +++++--- .../featureFlagController/minimumAppVersion/index.ts | 2 +- app/util/test/initial-background-state.json | 4 ++++ 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts index b54e6d32fc5..9d210df052e 100644 --- a/app/core/Engine/Engine.test.ts +++ b/app/core/Engine/Engine.test.ts @@ -37,6 +37,7 @@ describe('Engine', () => { expect(engine.context).toHaveProperty('NetworkController'); expect(engine.context).toHaveProperty('PhishingController'); expect(engine.context).toHaveProperty('PreferencesController'); + expect(engine.context).toHaveProperty('RemoteFeatureFlagController'); expect(engine.context).toHaveProperty('SignatureController'); expect(engine.context).toHaveProperty('TokenBalancesController'); expect(engine.context).toHaveProperty('TokenRatesController'); diff --git a/app/core/EngineService/EngineService.test.ts b/app/core/EngineService/EngineService.test.ts index 9c2d6a457da..2d5d0fcacf4 100644 --- a/app/core/EngineService/EngineService.test.ts +++ b/app/core/EngineService/EngineService.test.ts @@ -51,6 +51,7 @@ jest.mock('../Engine', () => { NetworkController: { subscribe: jest.fn() }, PhishingController: { subscribe: jest.fn() }, PreferencesController: { subscribe: jest.fn() }, + RemoteFeatureFlagController: { subscribe: jest.fn() }, TokenBalancesController: { subscribe: jest.fn() }, TokenRatesController: { subscribe: jest.fn() }, TransactionController: { subscribe: jest.fn() }, diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.test.ts b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts index 7ffaafdfea4..826b638ff46 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.test.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.test.ts @@ -1,7 +1,9 @@ import { RootState } from '../../../reducers'; import mockedEngine from '../../../core/__mocks__/MockedEngine'; import { mockedState, mockedEmptyFlagsState } from '../mocks'; +import { FEATURE_FLAG_NAME } from './types'; import { + featureFlagFallback, selectMobileMinimumVersions } from '.'; @@ -30,8 +32,8 @@ describe('Feature flag: minimumAppVersion', () => { appleMinimumOS, androidMinimumAPIVersion, } = selectMobileMinimumVersions(mockedEmptyFlagsState as RootState); - expect(appMinimumBuild).toBe(1024); - expect(appleMinimumOS).toBe(1025); - expect(androidMinimumAPIVersion).toBe(1026); + expect(appMinimumBuild).toEqual(featureFlagFallback[FEATURE_FLAG_NAME].appMinimumBuild); + expect(appleMinimumOS).toEqual(featureFlagFallback[FEATURE_FLAG_NAME].appleMinimumOS); + expect(androidMinimumAPIVersion).toEqual(featureFlagFallback[FEATURE_FLAG_NAME].androidMinimumAPIVersion); }); }); diff --git a/app/selectors/featureFlagController/minimumAppVersion/index.ts b/app/selectors/featureFlagController/minimumAppVersion/index.ts index 75e2ddd361c..be095bf88b8 100644 --- a/app/selectors/featureFlagController/minimumAppVersion/index.ts +++ b/app/selectors/featureFlagController/minimumAppVersion/index.ts @@ -6,7 +6,7 @@ import { UndefinedFeatureFlagType, } from './types'; -const featureFlagFallback: FeatureFlagType = { +export const featureFlagFallback: FeatureFlagType = { [FEATURE_FLAG_NAME]: { appMinimumBuild: 1243, appleMinimumOS: 6, diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 97eb70731c2..5a811034918 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -315,6 +315,10 @@ "PermissionController": { "subjects": {} }, + "RemoteFeatureFlagController": { + "cacheTimestamp": 0, + "remoteFeatureFlags": [] + }, "SelectedNetworkController": { "domains": {} }, From d7d384c00dc19b00e4e16b619216dca186625c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:13:00 +0000 Subject: [PATCH 28/30] migration: remove featureFlags property --- app/store/migrations/061.test.ts | 59 ++++++++++++++++++++++++++++++++ app/store/migrations/061.ts | 12 +++++++ app/store/migrations/index.ts | 4 ++- 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 app/store/migrations/061.test.ts create mode 100644 app/store/migrations/061.ts diff --git a/app/store/migrations/061.test.ts b/app/store/migrations/061.test.ts new file mode 100644 index 00000000000..6e0ba200cd2 --- /dev/null +++ b/app/store/migrations/061.test.ts @@ -0,0 +1,59 @@ +import migrate from './061'; +import { captureException } from '@sentry/react-native'; +import mockedEngine from '../../core/__mocks__/MockedEngine'; + +jest.mock('@sentry/react-native', () => ({ + captureException: jest.fn(), +})); +const mockedCaptureException = jest.mocked(captureException); + +jest.mock('../../core/Engine', () => ({ + init: () => mockedEngine.init(), +})); + +describe('Migration #61 - remove featureFlags property from redux state', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + const invalidStates = [ + { + state: null, + errorMessage: "FATAL ERROR: Migration 61: Invalid state error: 'object'", + scenario: 'state is invalid', + }, + ]; + + for (const { errorMessage, scenario, state } of invalidStates) { + it(`should capture exception if ${scenario}`, async () => { + const newState = await migrate(state); + + expect(newState).toStrictEqual(state); + expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error)); + expect(mockedCaptureException.mock.calls[0][0].message).toBe( + errorMessage, + ); + }); + } + + it('remove featureFlags property from redux state', async () => { + const oldState = { + engine: { + backgroundState: {}, + }, + featureFlags: { + minimumAppVersion: 29, + }, + }; + + const expectedState = { + engine: { + backgroundState: {}, + }, + }; + + const migratedState = await migrate(oldState); + expect(migratedState).toStrictEqual(expectedState); + }); +}); diff --git a/app/store/migrations/061.ts b/app/store/migrations/061.ts new file mode 100644 index 00000000000..b6b3cc42e75 --- /dev/null +++ b/app/store/migrations/061.ts @@ -0,0 +1,12 @@ +import { hasProperty } from '@metamask/utils'; +import { ensureValidState } from "./util"; + +export default function migrate(state: unknown) { + if (!ensureValidState(state, 61)) { + return state; + } + if (hasProperty(state, 'featureFlags')) { + delete state.featureFlags; + } + return state; +} diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index a1b929c96c7..5d6408a2959 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -61,6 +61,7 @@ import migration57 from './057'; import migration58 from './058'; import migration59 from './059'; import migration60 from './060'; +import migration61 from './061'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -133,7 +134,8 @@ export const migrationList: MigrationsList = { 57: migration57, 58: migration58, 59: migration59, - 60: migration60 + 60: migration60, + 61: migration61 }; // Enable both synchronous and asynchronous migrations From b784e89e226916a201d204ee80cdb52bc0fe7292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:22:33 +0000 Subject: [PATCH 29/30] codeowners: featureFlagController selector base files pattern --- .github/CODEOWNERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f7d8d322eaf..96b4b4dbbbd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,9 +17,7 @@ app/core/Engine/controllers/RemoteFeatureFlagController/ @MetaMask/mobile-pla app/core/Analytics/ @MetaMask/mobile-platform app/util/metrics/ @MetaMask/mobile-platform app/components/hooks/useMetrics/ @MetaMask/mobile-platform -app/selectors/featureFlagController/index.ts @MetaMask/mobile-platform -app/selectors/featureFlagController/index.test.ts @MetaMask/mobile-platform -app/selectors/featureFlagController/mocks.ts @MetaMask/mobile-platform +app/selectors/featureFlagController/* @MetaMask/mobile-platform app/selectors/featureFlagController/minimumAppVersion/ @MetaMask/mobile-platform app/store/migrations/ @MetaMask/mobile-platform bitrise.yml @MetaMask/mobile-platform From 38292f7d51b3b4910634299b9cc2c95946071268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <175489935+joaoloureirop@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:36:55 +0000 Subject: [PATCH 30/30] update useMinimumVersions.tsx import paths Co-authored-by: Nico MASSART --- app/components/hooks/MinimumVersions/useMinimumVersions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx index 26ffa9e9101..e74b5f11ff8 100644 --- a/app/components/hooks/MinimumVersions/useMinimumVersions.tsx +++ b/app/components/hooks/MinimumVersions/useMinimumVersions.tsx @@ -4,9 +4,9 @@ import { createUpdateNeededNavDetails } from '../../UI/UpdateNeeded/UpdateNeeded import { useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { InteractionManager } from 'react-native'; -import { SecurityState } from '../../../../app/reducers/security'; -import { RootState } from '../../../../app/reducers'; -import { selectAppMinimumBuild } from '../../../../app/selectors/featureFlagController/minimumAppVersion'; +import { SecurityState } from '../../../reducers/security'; +import { RootState } from '../../../reducers'; +import { selectAppMinimumBuild } from '../../../selectors/featureFlagController/minimumAppVersion'; const useMinimumVersions = () => { const { automaticSecurityChecksEnabled }: SecurityState = useSelector(