diff --git a/packages/shared/comms/sagas.ts b/packages/shared/comms/sagas.ts index 47784cb38..2baf653ef 100644 --- a/packages/shared/comms/sagas.ts +++ b/packages/shared/comms/sagas.ts @@ -1,4 +1,4 @@ -import { put, takeEvery, select, call, takeLatest, fork, take, race, delay, apply } from 'redux-saga/effects' +import { put, takeEvery, select, call, fork, take, race, delay, apply } from 'redux-saga/effects' import { commsEstablished, establishingComms, FATAL_ERROR } from 'shared/loading/types' import { commsLogger } from './context' @@ -27,7 +27,7 @@ import { EventChannel } from 'redux-saga' import { ExplorerIdentity } from 'shared/session/types' import { USER_AUTHENTIFIED } from 'shared/session/actions' import * as rfc4 from '@dcl/protocol/out-ts/decentraland/kernel/comms/rfc4/comms.gen' -import { selectAndReconnectRealm } from 'shared/dao/sagas' +import { clearRealmFromQueryString, selectAndReconnectRealm } from 'shared/dao/sagas' import { waitForMetaConfigurationInitialization } from 'shared/meta/sagas' import { getCommsConfig, getFeatureFlagEnabled, getMaxVisiblePeers } from 'shared/meta/selectors' import { getCurrentIdentity } from 'shared/session/selectors' @@ -58,12 +58,13 @@ import { import { getUnityInstance } from 'unity-interface/IUnityInterface' import { NotificationType } from 'shared/types' import { trackEvent } from 'shared/analytics' +import { TeleportController } from 'shared/world/TeleportController' const TIME_BETWEEN_PROFILE_RESPONSES = 1000 const INTERVAL_ANNOUNCE_PROFILE = 1000 export function* commsSaga() { - yield takeLatest(HANDLE_ROOM_DISCONNECTION, handleRoomDisconnectionSaga) + yield takeEvery(HANDLE_ROOM_DISCONNECTION, handleRoomDisconnectionSaga) yield takeEvery(FATAL_ERROR, function* () { // set null context on fatal error. this will bring down comms. @@ -260,6 +261,8 @@ function* handleConnectToComms(action: ConnectToCommsAction) { notifyStatusThroughChat('Error connecting to comms. Will try another realm') yield put(setRealmAdapter(undefined)) yield put(setRoomConnection(undefined)) + yield call(clearRealmFromQueryString) + yield call(selectAndReconnectRealm) } } @@ -556,16 +559,43 @@ export async function disconnectRoom(context: RoomConnection) { } } +function showErrorNotification(message: string) { + getUnityInstance().ShowNotification({ + type: NotificationType.GENERIC, + message, + buttonMessage: 'OK', + timer: 15 + }) +} + // this saga handles the suddenly disconnection of a CommsContext function* handleRoomDisconnectionSaga(action: HandleRoomDisconnection) { const room: RoomConnection = yield select(getCommsRoom) + // only if we are receiving an action corresponding to the current state if (room && room === action.payload.context) { // this also remove the context yield put(setRoomConnection(undefined)) if (action.payload.context) { - notifyStatusThroughChat(`Lost connection to realm`) + showErrorNotification(`Lost connection to realm`) + } + + const realm: IRealmAdapter | undefined = yield select(getRealmAdapter) + // when the RoomConnection gets disconnected, it may be necessary to bring + // the user to a functional realm. The realm will be chosen by the same + // algorithm as the one used when the application starts. + // ONLY IF the current realm has a fixedAdapter. otherwise, archipelago may + // assign another realm + if (!realm || realm.about.comms?.fixedAdapter) { + yield call(clearRealmFromQueryString) + + // if it was a WORLD, then we take the user to their home-point (if possible) + if (realm?.about.configurations?.realmName?.endsWith('.dcl.eth')) { + yield apply(TeleportController, TeleportController.goToHome, []) + } + + yield call(selectAndReconnectRealm) } } } diff --git a/packages/shared/dao/actions.ts b/packages/shared/dao/actions.ts index 722e53f78..d120d2963 100644 --- a/packages/shared/dao/actions.ts +++ b/packages/shared/dao/actions.ts @@ -13,7 +13,3 @@ export type SetCatalystCandidates = ReturnType export const SELECT_NETWORK = '[DAO] Select network' export const selectNetwork = (network: ETHEREUM_NETWORK) => action(SELECT_NETWORK, network) export type SelectNetworkAction = ReturnType - -export const CATALYST_REALMS_SCAN_REQUESTED = '[Request] Catalyst Realms scan' -export const catalystRealmsScanRequested = () => action(CATALYST_REALMS_SCAN_REQUESTED) -export type CatalystRealmsScanRequested = ReturnType diff --git a/packages/shared/dao/sagas.ts b/packages/shared/dao/sagas.ts index 35e0ea1be..0ce944fd1 100644 --- a/packages/shared/dao/sagas.ts +++ b/packages/shared/dao/sagas.ts @@ -1,9 +1,4 @@ -import { - setCatalystCandidates, - SET_CATALYST_CANDIDATES, - SetCatalystCandidates, - catalystRealmsScanRequested -} from './actions' +import { setCatalystCandidates, SET_CATALYST_CANDIDATES, SetCatalystCandidates } from './actions' import { call, put, takeEvery, select, take } from 'redux-saga/effects' import { PIN_CATALYST, ETHEREUM_NETWORK, PREVIEW, rootURLPreviewMode } from 'config' import { waitForMetaConfigurationInitialization, waitForNetworkSelected } from '../meta/sagas' @@ -77,12 +72,12 @@ function* pickCatalystRealm() { return urlWithProtocol(realm.hostname) } -function qsRealm() { +function getRealmFromQueryString() { const qs = new URLSearchParams(document.location.search) return qs.get('realm') } -function clearQsRealm() { +export function clearRealmFromQueryString() { const q = new URLSearchParams(globalThis.location.search) q.delete('realm') globalThis.history.replaceState({}, 'realm', `?${q.toString()}`) @@ -114,7 +109,7 @@ export function* selectAndReconnectRealm() { // if no realm was selected, then do the whole initialization dance } catch (e: any) { // if it failed, try changing the queryString - clearQsRealm() + clearRealmFromQueryString() try { // and try again yield call(tryConnectRealm) @@ -135,17 +130,16 @@ function* waitForCandidates() { function* selectRealm() { const network: ETHEREUM_NETWORK = yield call(waitForNetworkSelected) - yield call(initializeCatalystCandidates) - const candidatesReceived = yield select(getCatalystCandidatesReceived) if (!candidatesReceived) { + yield call(initializeCatalystCandidates) yield call(waitForCandidates) } const realm: string | undefined = // query param (dao candidates & cached) - (yield call(qsRealm)) || + (yield call(getRealmFromQueryString)) || // preview mode (PREVIEW ? rootURLPreviewMode() : null) || // CATALYST from url parameter @@ -177,7 +171,6 @@ async function getRealmFromLocalStorage(network: ETHEREUM_NETWORK) { function* initializeCatalystCandidates() { yield call(waitForMetaConfigurationInitialization) - yield put(catalystRealmsScanRequested()) const catalystsNodesEndpointURL: string | undefined = yield select(getCatalystNodesEndpoint) diff --git a/packages/shared/realm/selectors.ts b/packages/shared/realm/selectors.ts index 84956c821..716d76016 100644 --- a/packages/shared/realm/selectors.ts +++ b/packages/shared/realm/selectors.ts @@ -5,6 +5,14 @@ import { SET_REALM_ADAPTER } from './actions' import { realmToConnectionString, urlWithProtocol } from './resolver' import { IRealmAdapter, RootRealmState, OFFLINE_REALM } from './types' +export const isWorldLoaderActive = (realmAdapter: IRealmAdapter) => + !!realmAdapter?.about.configurations?.scenesUrn?.length || + realmAdapter?.about.configurations?.cityLoaderContentServer === '' + +export function isWorldActiveSelector(state: RootRealmState) { + return state.realm.realmAdapter && isWorldLoaderActive(state.realm.realmAdapter) +} + export const getRealmAdapter = (state: RootRealmState): IRealmAdapter | undefined => state.realm.realmAdapter export const getRealmConnectionString = (state: RootRealmState): string => state.realm.realmAdapter ? realmToConnectionString(state.realm.realmAdapter) : OFFLINE_REALM diff --git a/packages/shared/scene-loader/sagas.ts b/packages/shared/scene-loader/sagas.ts index 2ad9a63c9..b6d047285 100644 --- a/packages/shared/scene-loader/sagas.ts +++ b/packages/shared/scene-loader/sagas.ts @@ -30,7 +30,7 @@ import { getSceneLoader, getPositionSettled } from './selectors' -import { getFetchContentServerFromRealmAdapter } from 'shared/realm/selectors' +import { getFetchContentServerFromRealmAdapter, isWorldLoaderActive } from 'shared/realm/selectors' import { ISceneLoader, SceneLoaderPositionReport, SetDesiredScenesCommand } from './types' import { getSceneWorkerBySceneID, setDesiredParcelScenes } from 'shared/world/parcelSceneManager' import { BEFORE_UNLOAD } from 'shared/actions' @@ -197,10 +197,9 @@ function* setSceneLoaderOnSetRealmAction(action: SetRealmAdapterAction) { } else { // if the /about endpoint returns scenesUrn(s) then those need to be loaded // and the genesis city should not start - const loadFixedWorld = - !!adapter.about.configurations?.scenesUrn?.length || adapter.about.configurations?.cityLoaderContentServer === '' + const willLoadFixedWorld = isWorldLoaderActive(adapter) - if (loadFixedWorld) { + if (willLoadFixedWorld) { // TODO: disable green blockers here const loader: ISceneLoader = yield call(createWorldLoader, { diff --git a/packages/shared/voiceChat/selectors.ts b/packages/shared/voiceChat/selectors.ts index b7af6f4ac..1d9863a13 100644 --- a/packages/shared/voiceChat/selectors.ts +++ b/packages/shared/voiceChat/selectors.ts @@ -4,7 +4,7 @@ import { isFriend } from 'shared/friends/selectors' import { RootFriendsState } from 'shared/friends/types' import { getBannedUsers } from 'shared/meta/selectors' import { BannedUsers, RootMetaState } from 'shared/meta/types' -import { getCurrentUserProfile, getProfile } from 'shared/profiles/selectors' +import { getProfile } from 'shared/profiles/selectors' import { RootProfileState } from 'shared/profiles/types' import { RootVoiceChatState, VoicePolicy } from './types' import { VOICE_CHAT_FEATURE_TOGGLE } from 'shared/types' @@ -12,7 +12,6 @@ import { RootWorldState } from 'shared/world/types' import { getCurrentIdentity } from 'shared/session/selectors' import { RootSessionState } from 'shared/session/types' import { getSceneWorkerBySceneID } from 'shared/world/parcelSceneManager' -import { store } from '../store/isolatedStore' export const hasJoinedVoiceChat = (store: RootVoiceChatState) => store.voiceChat.joined