diff --git a/package-lock.json b/package-lock.json index d743ae847..d9e881a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@dcl/catalyst-peer": "^1.0.4", "@dcl/ecs-math": "^1.0.1", "@dcl/ecs-quests": "^1.3.1", + "@dcl/feature-flags": "^1.0.1", "@dcl/hashing": "^1.1.0", "@dcl/kernel-interface": "^2.0.0-20210922153939.commit-017905d", "@dcl/legacy-ecs": "^6.10.1-2191620277.commit-7b74643", @@ -416,6 +417,11 @@ "prettier": "^2.4.1" } }, + "node_modules/@dcl/feature-flags": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@dcl/feature-flags/-/feature-flags-1.0.1.tgz", + "integrity": "sha512-DIiLY5JwTjWJWFKcW8PO76NIwHbKsv8W0brHzHUWvZGGhaXV34SJRKpgnftIP5Buipok4W+d7x66BknJRmK4AA==" + }, "node_modules/@dcl/hashing": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@dcl/hashing/-/hashing-1.1.0.tgz", @@ -4464,15 +4470,15 @@ "dev": true }, "node_modules/dcl-catalyst-client": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/dcl-catalyst-client/-/dcl-catalyst-client-11.2.0.tgz", - "integrity": "sha512-D2W0Qcgpu9YrBPMtWYJPsOrAxYmkBLEGJADrNpNzhO4jxRxBYj2A6dKWatHcu28a/hrfqrt/pISjLJgzDPvy7Q==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/dcl-catalyst-client/-/dcl-catalyst-client-11.3.2.tgz", + "integrity": "sha512-tc5dliQvMKD2XQlf96AMjrHOQHKrbC+z0veEh5sNqbkD44HlIZw36Sb4O65Te1VmouziKHt7EkxiLqyXP6jxVg==", "dependencies": { - "@dcl/hashing": "^1.0.0", + "@dcl/hashing": "^1.1.0", "@dcl/schemas": "^4.0.0", "@types/form-data": "^2.5.0", "cookie": "^0.4.1", - "dcl-catalyst-commons": "8.1.2", + "dcl-catalyst-commons": "^8.2.0", "dcl-crypto": "^2.2.0", "form-data": "^4.0.0" } @@ -4486,12 +4492,12 @@ } }, "node_modules/dcl-catalyst-commons": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/dcl-catalyst-commons/-/dcl-catalyst-commons-8.1.2.tgz", - "integrity": "sha512-MLgjSskdANljg3Ie68UYPmXdvEEziJPPmTlMv7mwoiYJ0D76H0ixCQOnCjw7/+uzl3rSm0p0ofjtHYufHoVlqw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/dcl-catalyst-commons/-/dcl-catalyst-commons-8.2.1.tgz", + "integrity": "sha512-C9wu8lHox+f1DbsJ0T6izupftmYnvpsRDh/DcTUONOUasLtkzbirlmYZYG8LksVY5VdSGIUuyfp+9WWprLmsSw==", "dependencies": { "@dcl/hashing": "^1.0.0", - "@dcl/schemas": "4.1.0", + "@dcl/schemas": "4.8.0", "abort-controller": "^3.0.0", "cids": "^1.1.9", "cookie": "^0.4.1", @@ -4505,29 +4511,6 @@ "npm": ">=7.0.0" } }, - "node_modules/dcl-catalyst-commons/node_modules/@dcl/schemas": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-4.1.0.tgz", - "integrity": "sha512-NCKWoK1KtrMlsP0hRM/4Btb4+o48gpi6ZCaEyDnDLYwuCJcApXISB3qaQY/FzdKuyfx6C3MEbjeCGZbhlEIhpQ==", - "dependencies": { - "ajv": "^7.1.0" - } - }, - "node_modules/dcl-catalyst-commons/node_modules/ajv": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz", - "integrity": "sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/dcl-catalyst-commons/node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -4536,11 +4519,6 @@ "node": ">= 0.6" } }, - "node_modules/dcl-catalyst-commons/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/dcl-catalyst-commons/node_modules/node-fetch": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", @@ -14414,9 +14392,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", "engines": { "node": ">= 8" } @@ -15184,6 +15162,11 @@ "dev": true, "requires": {} }, + "@dcl/feature-flags": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@dcl/feature-flags/-/feature-flags-1.0.1.tgz", + "integrity": "sha512-DIiLY5JwTjWJWFKcW8PO76NIwHbKsv8W0brHzHUWvZGGhaXV34SJRKpgnftIP5Buipok4W+d7x66BknJRmK4AA==" + }, "@dcl/hashing": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@dcl/hashing/-/hashing-1.1.0.tgz", @@ -18352,15 +18335,15 @@ "dev": true }, "dcl-catalyst-client": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/dcl-catalyst-client/-/dcl-catalyst-client-11.2.0.tgz", - "integrity": "sha512-D2W0Qcgpu9YrBPMtWYJPsOrAxYmkBLEGJADrNpNzhO4jxRxBYj2A6dKWatHcu28a/hrfqrt/pISjLJgzDPvy7Q==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/dcl-catalyst-client/-/dcl-catalyst-client-11.3.2.tgz", + "integrity": "sha512-tc5dliQvMKD2XQlf96AMjrHOQHKrbC+z0veEh5sNqbkD44HlIZw36Sb4O65Te1VmouziKHt7EkxiLqyXP6jxVg==", "requires": { - "@dcl/hashing": "^1.0.0", + "@dcl/hashing": "^1.1.0", "@dcl/schemas": "^4.0.0", "@types/form-data": "^2.5.0", "cookie": "^0.4.1", - "dcl-catalyst-commons": "8.1.2", + "dcl-catalyst-commons": "^8.2.0", "dcl-crypto": "^2.2.0", "form-data": "^4.0.0" }, @@ -18373,12 +18356,12 @@ } }, "dcl-catalyst-commons": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/dcl-catalyst-commons/-/dcl-catalyst-commons-8.1.2.tgz", - "integrity": "sha512-MLgjSskdANljg3Ie68UYPmXdvEEziJPPmTlMv7mwoiYJ0D76H0ixCQOnCjw7/+uzl3rSm0p0ofjtHYufHoVlqw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/dcl-catalyst-commons/-/dcl-catalyst-commons-8.2.1.tgz", + "integrity": "sha512-C9wu8lHox+f1DbsJ0T6izupftmYnvpsRDh/DcTUONOUasLtkzbirlmYZYG8LksVY5VdSGIUuyfp+9WWprLmsSw==", "requires": { "@dcl/hashing": "^1.0.0", - "@dcl/schemas": "4.1.0", + "@dcl/schemas": "4.8.0", "abort-controller": "^3.0.0", "cids": "^1.1.9", "cookie": "^0.4.1", @@ -18388,35 +18371,11 @@ "node-fetch": "^3.0.0" }, "dependencies": { - "@dcl/schemas": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-4.1.0.tgz", - "integrity": "sha512-NCKWoK1KtrMlsP0hRM/4Btb4+o48gpi6ZCaEyDnDLYwuCJcApXISB3qaQY/FzdKuyfx6C3MEbjeCGZbhlEIhpQ==", - "requires": { - "ajv": "^7.1.0" - } - }, - "ajv": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz", - "integrity": "sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node-fetch": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", @@ -26237,9 +26196,9 @@ } }, "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" }, "web3x": { "version": "4.0.6", diff --git a/package.json b/package.json index 4ac21b13a..48c848932 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@dcl/catalyst-peer": "^1.0.4", "@dcl/ecs-math": "^1.0.1", "@dcl/ecs-quests": "^1.3.1", + "@dcl/feature-flags": "^1.0.1", "@dcl/hashing": "^1.1.0", "@dcl/kernel-interface": "^2.0.0-20210922153939.commit-017905d", "@dcl/legacy-ecs": "^6.10.1-2191620277.commit-7b74643", diff --git a/packages/config/index.ts b/packages/config/index.ts index 70fc9ff37..5022fc2c3 100644 --- a/packages/config/index.ts +++ b/packages/config/index.ts @@ -228,16 +228,12 @@ export function getServerConfigurations(network: ETHEREUM_NETWORK) { const tld = network === ETHEREUM_NETWORK.MAINNET ? 'org' : 'zone' const metaConfigBaseUrl = META_CONFIG_URL || `https://config.decentraland.${tld}/explorer.json` - const metaFeatureFlagsBaseUrl = PREVIEW - ? `${rootURLPreviewMode()}/feature-flags/explorer.json` - : `https://feature-flags.decentraland.${tld}/explorer.json` const questsUrl = ensureSingleString(qs.get('QUESTS_SERVER_URL')) ?? `https://quests-api.decentraland.${network ? 'org' : 'io'}` return { explorerConfiguration: `${metaConfigBaseUrl}?t=${new Date().getTime()}`, - explorerFeatureFlags: `${metaFeatureFlagsBaseUrl}?t=${new Date().getTime()}`, questsUrl } } diff --git a/packages/entryPoints/index.ts b/packages/entryPoints/index.ts index 8594f6539..ba813f76a 100644 --- a/packages/entryPoints/index.ts +++ b/packages/entryPoints/index.ts @@ -22,8 +22,8 @@ import { foregroundChangeObservable, isForeground } from 'shared/world/worldStat import { getCurrentIdentity } from 'shared/session/selectors' import { realmInitialized } from 'shared/dao' import { ensureMetaConfigurationInitialized } from 'shared/meta' -import { FeatureFlags, WorldConfig } from 'shared/meta/types' -import { getFeatureFlags, getWorldConfig, isFeatureEnabled } from 'shared/meta/selectors' +import { WorldConfig } from 'shared/meta/types' +import { getFeatureFlagEnabled, getFeatureFlags, getWorldConfig } from 'shared/meta/selectors' import { kernelConfigForRenderer } from '../unity-interface/kernelConfigForRenderer' import { ensureUnityInterface } from 'shared/renderer' import { globalObservable } from 'shared/observables' @@ -162,13 +162,13 @@ async function loadWebsiteSystems(options: KernelOptions['kernelOptions']) { // For example disable AssetBundles needs a system from FeatureFlag i.SetFeatureFlagsConfiguration(getFeatureFlags(store.getState())) - const questEnabled = isFeatureEnabled(store.getState(), FeatureFlags.QUESTS, false) + const questEnabled = getFeatureFlagEnabled(store.getState(), 'quests') const worldConfig: WorldConfig | undefined = getWorldConfig(store.getState()) const renderProfile = worldConfig ? worldConfig.renderProfile ?? RenderProfile.DEFAULT : RenderProfile.DEFAULT i.SetRenderProfile(renderProfile) // killswitch, disable asset bundles - if (!isFeatureEnabled(store.getState(), FeatureFlags.ASSET_BUNDLES, false)) { + if (!getFeatureFlagEnabled(store.getState(), 'asset_bundles')) { i.SetDisableAssetBundles() } @@ -196,10 +196,7 @@ async function loadWebsiteSystems(options: KernelOptions['kernelOptions']) { i.ConfigureHUDElement( HUDElementID.TASKBAR, { active: true, visible: true }, - { - enableVoiceChat: true, - enableQuestPanel: isFeatureEnabled(store.getState(), FeatureFlags.QUESTS, false) - } + { enableVoiceChat: true, enableQuestPanel: questEnabled } ) i.ConfigureHUDElement(HUDElementID.WORLD_CHAT_WINDOW, { active: true, visible: false }) i.ConfigureHUDElement(HUDElementID.CONTROLS_HUD, { active: true, visible: false }) @@ -233,10 +230,11 @@ async function loadWebsiteSystems(options: KernelOptions['kernelOptions']) { i.ConfigureTutorial(profile.tutorialStep, tutorialConfig) const isGuest = !identity.hasConnectedWeb3 - const BUILDER_IN_WORLD_ENABLED = !isGuest && isFeatureEnabled(store.getState(), FeatureFlags.BUILDER_IN_WORLD, false) + const friendsActivated = !isGuest && !getFeatureFlagEnabled(store.getState(), 'matrix_disabled') + const BUILDER_IN_WORLD_ENABLED = !isGuest && getFeatureFlagEnabled(store.getState(), 'builder_in_world') i.ConfigureHUDElement(HUDElementID.BUILDER_PROJECTS_PANEL, { active: BUILDER_IN_WORLD_ENABLED, visible: false }) - i.ConfigureHUDElement(HUDElementID.FRIENDS, { active: !isGuest, visible: false }) + i.ConfigureHUDElement(HUDElementID.FRIENDS, { active: friendsActivated, visible: false }) await realmInitialized() diff --git a/packages/shared/apis/EnvironmentAPI.ts b/packages/shared/apis/EnvironmentAPI.ts index e8e926364..08780f88a 100644 --- a/packages/shared/apis/EnvironmentAPI.ts +++ b/packages/shared/apis/EnvironmentAPI.ts @@ -6,8 +6,7 @@ import { getServerConfigurations, PREVIEW, RENDERER_WS } from 'config' import { store } from 'shared/store/isolatedStore' import { getCommsIsland, getRealm } from 'shared/comms/selectors' import { Realm } from 'shared/dao/types' -import { isFeatureEnabled } from 'shared/meta/selectors' -import { FeatureFlags } from 'shared/meta/types' +import { getFeatureFlagEnabled } from 'shared/meta/selectors' import { EnvironmentRealm, ExplorerConfiguration, IEnvironmentAPI, Platform } from './IEnvironmentAPI' type DecentralandTimeData = { @@ -56,7 +55,7 @@ export class EnvironmentAPI extends ExposableAPI implements IEnvironmentAPI { */ @exposeMethod async areUnsafeRequestAllowed(): Promise { - return isFeatureEnabled(store.getState(), FeatureFlags.UNSAFE_FETCH_AND_WEBSOCKET, false) + return getFeatureFlagEnabled(store.getState(), 'unsafe-request') } /** diff --git a/packages/shared/comms/v1/brokerWorldInstanceConnection.ts b/packages/shared/comms/v1/brokerWorldInstanceConnection.ts index 2fc25a489..9e195ac41 100644 --- a/packages/shared/comms/v1/brokerWorldInstanceConnection.ts +++ b/packages/shared/comms/v1/brokerWorldInstanceConnection.ts @@ -229,6 +229,7 @@ export class BrokerWorldInstanceConnection implements RoomConnection { async disconnect() { if (this.pingInterval) { + this.events.emit('DISCONNECTION') clearInterval(this.pingInterval) } await this.broker.disconnect() diff --git a/packages/shared/dao/sagas.ts b/packages/shared/dao/sagas.ts index 88aeb60e9..8cb55b11b 100644 --- a/packages/shared/dao/sagas.ts +++ b/packages/shared/dao/sagas.ts @@ -123,6 +123,14 @@ function* waitForCandidates() { } } +function realmFromPinnedCatalyst(): Realm { + return { + protocol: 'v2', + hostname: PIN_CATALYST || 'peer.decentraland.org', + serverName: 'pinned-catalyst' + } +} + function* selectRealm() { const network: ETHEREUM_NETWORK = yield call(waitForNetworkSelected) @@ -147,13 +155,15 @@ function* selectRealm() { } const realm: Realm | undefined = - // 1st priority: query param (dao candidates & cached) + // query param (dao candidates & cached) (yield call(getConfiguredRealm, [...allCandidates, ...cachedCandidates])) || - // 2nd priority: preview mode + // CATALYST from url parameter + (PIN_CATALYST ? realmFromPinnedCatalyst() : null) || + // preview mode (PREVIEW ? PREVIEW_REALM : null) || - // 3rd priority: cached in local storage + // cached in local storage (yield call(getRealmFromLocalStorage, network)) || - // 4th priority: fetch catalysts and select one using the load balancing + // fetch catalysts and select one using the load balancing (yield call(pickCatalystRealm)) if (!realm) debugger @@ -195,7 +205,7 @@ function* initializeCatalystCandidates() { const catalystsNodesEndpointURL: string | undefined = yield select(getCatalystNodesEndpoint) const nodes: CatalystNode[] = yield call(fetchCatalystRealms, catalystsNodesEndpointURL) - const added: string[] = PIN_CATALYST ? [] : yield select(getAddedServers) + const added: string[] = yield select(getAddedServers) const candidates: Candidate[] = yield call(fetchCatalystStatuses, added.map((url) => ({ domain: url })).concat(nodes)) diff --git a/packages/shared/friends/sagas.ts b/packages/shared/friends/sagas.ts index a7a9c2b3c..3a7cf79c0 100644 --- a/packages/shared/friends/sagas.ts +++ b/packages/shared/friends/sagas.ts @@ -53,7 +53,7 @@ import { import { waitForRealmInitialized } from 'shared/dao/sagas' import { getUnityInstance } from 'unity-interface/IUnityInterface' import { ensureFriendProfile } from './ensureFriendProfile' -import { getSynapseUrl } from 'shared/meta/selectors' +import { getFeatureFlagEnabled, getSynapseUrl } from 'shared/meta/selectors' import { notifyStatusThroughChat } from 'shared/chat' import { SET_WORLD_CONTEXT } from 'shared/comms/actions' import { getRealm } from 'shared/comms/selectors' @@ -62,6 +62,7 @@ import { trackEvent } from '../analytics' import { getCurrentIdentity, getIsGuestLogin } from 'shared/session/selectors' import { store } from 'shared/store/isolatedStore' import { getPeer } from 'shared/comms/peers' +import { waitForMetaConfigurationInitialization } from 'shared/meta/sagas' const logger = DEBUG_KERNEL_LOG ? createLogger('chat: ') : createDummyLogger() @@ -88,7 +89,15 @@ export function* friendsSaga() { function* initializeFriendsSaga() { let secondsToRetry = MIN_TIME_BETWEEN_FRIENDS_INITIALIZATION_RETRIES_MILLIS - while (true) { + yield call(waitForMetaConfigurationInitialization) + + // this reconnection breaks the server. setting to false + const shouldRetryReconnection = yield select(getFeatureFlagEnabled, 'retry_matrix_login') + const chatDisabled = yield select(getFeatureFlagEnabled, 'matrix_disabled') + debugger + if (chatDisabled) return + + do { yield race({ auth: take(USER_AUTHENTIFIED), delay: delay(secondsToRetry) @@ -107,7 +116,7 @@ function* initializeFriendsSaga() { const client: SocialAPI | null = yield select(getSocialClient) const isLoggedIn: boolean = (currentIdentity && client && (yield apply(client, client.isLoggedIn, []))) || false - const shouldRetry = !isLoggedIn + const shouldRetry = !isLoggedIn && !isGuest if (shouldRetry) { try { @@ -130,7 +139,7 @@ function* initializeFriendsSaga() { } } } - } + } while (shouldRetryReconnection) } async function handleIncomingFriendshipUpdateStatus(action: FriendshipAction, socialId: string) { diff --git a/packages/shared/meta/reducer.ts b/packages/shared/meta/reducer.ts index 9dad80b69..f42d3c23f 100644 --- a/packages/shared/meta/reducer.ts +++ b/packages/shared/meta/reducer.ts @@ -2,7 +2,7 @@ import { AnyAction } from 'redux' import { META_CONFIGURATION_INITIALIZED } from './actions' import { MetaState } from './types' -const initialState = { +const initialState: MetaState = { initialized: false, config: {} } diff --git a/packages/shared/meta/sagas.ts b/packages/shared/meta/sagas.ts index 2530651e3..40fa0f375 100644 --- a/packages/shared/meta/sagas.ts +++ b/packages/shared/meta/sagas.ts @@ -4,38 +4,17 @@ import { FORCE_RENDERING_STYLE, getAssetBundlesBaseUrl, getServerConfigurations, - QS_MAX_VISIBLE_PEERS + PREVIEW, + rootURLPreviewMode } from 'config' import { META_CONFIGURATION_INITIALIZED, metaConfigurationInitialized } from './actions' import defaultLogger from '../logger' -import { BannedUsers, MetaConfiguration, WorldConfig } from './types' -import { isMetaConfigurationInitiazed } from './selectors' +import { FeatureFlagsName, MetaConfiguration, WorldConfig } from './types' +import { getMaxVisiblePeers, isMetaConfigurationInitiazed } from './selectors' import { getSelectedNetwork } from 'shared/dao/selectors' import { SELECT_NETWORK } from 'shared/dao/actions' -import { AlgorithmChainConfig } from 'shared/dao/pick-realm-algorithm/types' import { RootState } from 'shared/store/rootTypes' -import { DEFAULT_MAX_VISIBLE_PEERS } from '.' - -function valueFromVariants(variants: Record | undefined, key: string): T | undefined { - const variant = variants?.[key] - if (variant && variant.enabled) { - try { - return JSON.parse(variant.payload.value) - } catch (e) { - defaultLogger.warn(`Couldn't parse value for ${key} from variants. The variants response was: `, variants) - } - } -} - -function bannedUsersFromVariants(variants: Record | undefined): BannedUsers | undefined { - return valueFromVariants(variants, 'explorer-banned_users') -} - -function pickRealmAlgorithmConfigFromVariants( - variants: Record | undefined -): AlgorithmChainConfig | undefined { - return valueFromVariants(variants, 'explorer-pick_realm_algorithm_config') -} +import { FeatureFlagsResult, fetchFlags } from '@dcl/feature-flags' export function* waitForMetaConfigurationInitialization() { const configInitialized: boolean = yield select(isMetaConfigurationInitiazed) @@ -52,23 +31,12 @@ export function* waitForNetworkSelected() { return net } -function getMaxVisiblePeers(variants: Record | undefined): number { - if (QS_MAX_VISIBLE_PEERS !== undefined) return QS_MAX_VISIBLE_PEERS - const fromVariants = valueFromVariants(variants, 'explorer-max_visible_peers') - - return typeof fromVariants === 'number' ? fromVariants : DEFAULT_MAX_VISIBLE_PEERS -} - function* initMeta() { const net: ETHEREUM_NETWORK = yield call(waitForNetworkSelected) const config: Partial = yield call(fetchMetaConfiguration, net) - const flagsAndVariants: { flags: Record; variants: Record } | undefined = yield call( - fetchFeatureFlagsAndVariants, - net - ) - - const maxVisiblePeers = getMaxVisiblePeers(flagsAndVariants?.variants) + const flagsAndVariants: FeatureFlagsResult = yield call(fetchFeatureFlagsAndVariants, net) + const maxVisiblePeers: number = yield select(getMaxVisiblePeers) const merge: Partial = { ...config, @@ -76,10 +44,7 @@ function* initMeta() { ...config.comms, maxVisiblePeers }, - featureFlags: flagsAndVariants?.flags, - featureFlagsV2: flagsAndVariants, - bannedUsers: bannedUsersFromVariants(flagsAndVariants?.variants), - pickRealmAlgorithmConfig: pickRealmAlgorithmConfigFromVariants(flagsAndVariants?.variants) + featureFlagsV2: flagsAndVariants } if (FORCE_RENDERING_STYLE) { @@ -97,18 +62,49 @@ export function* metaSaga(): any { yield call(initMeta) } -async function fetchFeatureFlagsAndVariants(network: ETHEREUM_NETWORK): Promise | undefined> { - const featureFlagsEndpoint = getServerConfigurations(network).explorerFeatureFlags - try { - const response = await fetch(featureFlagsEndpoint, { - credentials: 'include' +async function fetchFeatureFlagsAndVariants(network: ETHEREUM_NETWORK): Promise { + const tld = network === ETHEREUM_NETWORK.MAINNET ? 'org' : 'zone' + + const explorerFeatureFlags = PREVIEW + ? `${rootURLPreviewMode()}/feature-flags` + : `https://feature-flags.decentraland.${tld}` + + const flagsAndVariants = await fetchFlags({ applicationName: 'explorer', featureFlagsUrl: explorerFeatureFlags }) + + for (const key in flagsAndVariants.flags) { + const value = flagsAndVariants.flags[key] + delete flagsAndVariants.flags[key] + flagsAndVariants.flags[key.replace(/^explorer-/, '')] = value + } + + for (const key in flagsAndVariants.variants) { + const value = flagsAndVariants.variants[key] + delete flagsAndVariants.variants[key] + value.name = key.replace(/^explorer-/, '') + flagsAndVariants.variants[value.name] = value + } + + if (location.search.length !== 0) { + const flags = new URLSearchParams(location.search) + flags.forEach((_, key) => { + if (key.startsWith(`DISABLE_`)) { + const featureName = key.replace('DISABLE_', '').toLowerCase() as FeatureFlagsName + flagsAndVariants.flags[featureName] = false + if (featureName in flagsAndVariants.variants) { + flagsAndVariants.variants[featureName].enabled = false + } + } else if (key.startsWith(`ENABLE_`)) { + const featureName = key.replace('ENABLE_', '').toLowerCase() as FeatureFlagsName + flagsAndVariants.flags[featureName] = true + if (featureName in flagsAndVariants.variants) { + flagsAndVariants.variants[featureName].enabled = true + } else { + } + } }) - if (response.ok) { - return response.json() - } - } catch (e) { - defaultLogger.warn(`Error while fetching feature flags from '${featureFlagsEndpoint}'. Using default config`) } + + return flagsAndVariants } async function fetchMetaConfiguration(network: ETHEREUM_NETWORK) { diff --git a/packages/shared/meta/selectors.ts b/packages/shared/meta/selectors.ts index fa4f76969..979c0260f 100644 --- a/packages/shared/meta/selectors.ts +++ b/packages/shared/meta/selectors.ts @@ -1,7 +1,8 @@ -import type { BannedUsers, CommsConfig, FeatureFlag, RootMetaState, WorldConfig } from './types' +import type { BannedUsers, CommsConfig, FeatureFlag, FeatureFlagsName, RootMetaState, WorldConfig } from './types' import type { Vector2Component } from 'atomicHelpers/landHelpers' import { AlgorithmChainConfig } from 'shared/dao/pick-realm-algorithm/types' import { DEFAULT_MAX_VISIBLE_PEERS } from '.' +import { QS_MAX_VISIBLE_PEERS } from 'config' export const getAddedServers = (store: RootMetaState): string[] => { const { config } = store.meta @@ -38,75 +39,54 @@ export const getPois = (store: RootMetaState): Vector2Component[] => getWorldCon export const getCommsConfig = (store: RootMetaState): CommsConfig => store.meta.config.comms ?? { maxVisiblePeers: DEFAULT_MAX_VISIBLE_PEERS } -export const getBannedUsers = (store: RootMetaState): BannedUsers => store.meta.config.bannedUsers ?? {} +export const getBannedUsers = (store: RootMetaState): BannedUsers => + (getFeatureFlagVariantValue(store, 'banned_users') as BannedUsers) ?? {} export const getPickRealmsAlgorithmConfig = (store: RootMetaState): AlgorithmChainConfig | undefined => - store.meta.config.pickRealmAlgorithmConfig + getFeatureFlagVariantValue(store, 'pick_realm_algorithm_config') as AlgorithmChainConfig | undefined + +export function getMaxVisiblePeers(store: RootMetaState): number { + if (QS_MAX_VISIBLE_PEERS !== undefined) return QS_MAX_VISIBLE_PEERS + const fromVariants = +(getFeatureFlagVariantValue(store, 'max_visible_peers') as string) + + return !isNaN(fromVariants) ? fromVariants : DEFAULT_MAX_VISIBLE_PEERS +} /** * Returns the variant content of a feature flag */ -export function getVariantContent(store: RootMetaState, featureName: string): string | undefined { +export function getFeatureFlagVariantValue(store: RootMetaState, featureName: FeatureFlagsName): unknown { const ff = getFeatureFlags(store) - if (ff.variants[featureName] && ff.variants[featureName].payload) { - return ff.variants[featureName].payload?.value + const variant = ff.variants[featureName]?.payload + if (variant) { + try { + if (variant.type === 'json') return JSON.parse(variant.value) + } catch (e) { + console.warn(`Couldn't parse value for ${featureName} from variants.`) + } + + return variant.value } return undefined } -export function getFeatureFlags(store: RootMetaState): FeatureFlag { - const featureFlag: FeatureFlag = { - flags: {}, - variants: {} - } - - if (store?.meta?.config?.featureFlagsV2 !== undefined) { - for (const feature in store?.meta?.config?.featureFlagsV2.flags) { - const featureName = feature.replace('explorer-', '') - featureFlag.flags[featureName] = store?.meta?.config?.featureFlagsV2.flags[feature] - } - - for (const feature in store?.meta?.config?.featureFlagsV2.variants) { - const featureName = feature.replace('explorer-', '') - featureFlag.variants[featureName] = store?.meta?.config?.featureFlagsV2.variants[feature] - featureFlag.variants[featureName].name = featureName - } - } - if (location.search.length !== 0) { - const flags = new URLSearchParams(location.search) - flags.forEach((_, key) => { - if (key.includes(`DISABLE_`)) { - const featureName = key.replace('DISABLE_', '').toLowerCase() - featureFlag.flags[featureName] = false - } else if (key.includes(`ENABLE_`)) { - const featureName = key.replace('ENABLE_', '').toLowerCase() - featureFlag.flags[featureName] = true - } - }) +/** + * Returns the feature flag value + */ +export function getFeatureFlagEnabled(store: RootMetaState, featureName: FeatureFlagsName): boolean { + const ff = getFeatureFlags(store) + if (ff.flags[featureName]) { + return ff.flags[featureName] || false } - return featureFlag + return false } -export const isFeatureEnabled = (store: RootMetaState, featureName: string, ifNotSet: boolean): boolean => { - const queryParamFlag = toUrlFlag(featureName) - if (location.search.includes(`DISABLE_${queryParamFlag}`)) { - return false - } else if (location.search.includes(`ENABLE_${queryParamFlag}`)) { - return true - } else { - const featureFlag = store?.meta.config?.featureFlags?.[`explorer-${featureName}`] - return featureFlag ?? ifNotSet - } +export function getFeatureFlags(store: RootMetaState): FeatureFlag { + return store.meta.config.featureFlagsV2 || { flags: {}, variants: {} } } export const getSynapseUrl = (store: RootMetaState): string => store.meta.config.synapseUrl ?? 'https://synapse.decentraland.io' -/** Convert camel case to upper snake case */ -function toUrlFlag(key: string) { - const result = key.replace(/([A-Z])/g, ' $1') - return result.split(' ').join('_').toUpperCase() -} - export const getCatalystNodesEndpoint = (store: RootMetaState): string | undefined => store.meta.config.servers?.catalystsNodesEndpoint diff --git a/packages/shared/meta/types.ts b/packages/shared/meta/types.ts index 96d90f330..7125730ed 100644 --- a/packages/shared/meta/types.ts +++ b/packages/shared/meta/types.ts @@ -1,6 +1,6 @@ import { Vector2Component } from 'atomicHelpers/landHelpers' import { RenderProfile } from 'shared/types' -import { AlgorithmChainConfig } from 'shared/dao/pick-realm-algorithm/types' +import { FeatureFlagVariant } from '@dcl/feature-flags' export type MetaConfiguration = { explorer: { @@ -13,16 +13,27 @@ export type MetaConfiguration = { contentWhitelist: string[] catalystsNodesEndpoint?: string } - pickRealmAlgorithmConfig?: AlgorithmChainConfig - bannedUsers: BannedUsers synapseUrl: string world: WorldConfig comms: CommsConfig minCatalystVersion?: string - featureFlags?: Record featureFlagsV2?: FeatureFlag } +export type FeatureFlagsName = + | 'quests' // quests feature + | 'retry_matrix_login' // retry matrix reconnection + | 'parcel-denylist' // denylist of specific parcels using variants + | 'matrix_disabled' // disable matrix integration entirely + | 'builder_in_world' + | 'avatar_lods' + | 'asset_bundles' + | 'explorev2' + | 'unsafe-request' + | 'pick_realm_algorithm_config' + | 'banned_users' + | 'max_visible_peers' + export type BannedUsers = Record export type Ban = { @@ -54,25 +65,7 @@ export type CommsConfig = { maxVisiblePeers: number } -export enum FeatureFlags { - QUESTS = 'quests', - BUILDER_IN_WORLD = 'builder_in_world', - AVATAR_LODS = 'avatar_lods', - ASSET_BUNDLES = 'asset_bundles', - EXPLORE_V2_ENABLED = 'explorev2', - UNSAFE_FETCH_AND_WEBSOCKET = 'unsafe-request' -} - export type FeatureFlag = { - flags: Record - variants: Record -} - -export type FeatureFlagVariant = { - name: string - enabled: boolean - payload?: { - type: string - value: string - } + flags: Partial> + variants: Partial> } diff --git a/packages/shared/quests/sagas.ts b/packages/shared/quests/sagas.ts index bf1555cd9..b97fee586 100644 --- a/packages/shared/quests/sagas.ts +++ b/packages/shared/quests/sagas.ts @@ -7,8 +7,7 @@ import { getUnityInstance } from 'unity-interface/IUnityInterface' import { toRendererQuest } from '@dcl/ecs-quests/@dcl/mappings' import { getPreviousQuests, getQuests } from './selectors' import { deepEqual } from 'atomicHelpers/deepEqual' -import { isFeatureEnabled } from '../meta/selectors' -import { FeatureFlags } from '../meta/types' +import { getFeatureFlagEnabled } from '../meta/selectors' import { waitForRendererInstance } from 'shared/renderer/sagas' import { waitForMetaConfigurationInitialization } from 'shared/meta/sagas' @@ -21,7 +20,7 @@ export function* questsSaga(): any { function* areQuestsEnabled() { yield call(waitForMetaConfigurationInitialization) - const ret: boolean = yield select(isFeatureEnabled, FeatureFlags.QUESTS, false) + const ret: boolean = yield select(getFeatureFlagEnabled, 'quests') return ret } diff --git a/packages/shared/world/parcelSceneManager.ts b/packages/shared/world/parcelSceneManager.ts index 29cae8294..e65998365 100644 --- a/packages/shared/world/parcelSceneManager.ts +++ b/packages/shared/world/parcelSceneManager.ts @@ -26,7 +26,7 @@ import { StatefulWorker } from './StatefulWorker' import { UnityScene } from 'unity-interface/UnityScene' import { Vector2Component } from 'atomicHelpers/landHelpers' import { PositionTrackEvents } from 'shared/analytics/types' -import { getVariantContent } from 'shared/meta/selectors' +import { getFeatureFlagVariantValue } from 'shared/meta/selectors' import { activateAllPortableExperiences, killAllPortableExperiences } from '../portableExperiences/actions' import { signalParcelLoadingStarted } from 'shared/renderer/actions' @@ -45,7 +45,7 @@ declare const globalThis: any const PARCEL_DENY_LISTED_FEATURE_FLAG = 'parcel-denylist' export function isParcelDenyListed(coordinates: string[]) { - const denylist = getVariantContent(store.getState(), PARCEL_DENY_LISTED_FEATURE_FLAG) + const denylist = getFeatureFlagVariantValue(store.getState(), PARCEL_DENY_LISTED_FEATURE_FLAG) as string const setOfCoordinates = new Set(coordinates)