Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

feat: teleport home and to a realm if the fixedAdapter is disconnected (worlds) #805

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
38 changes: 34 additions & 4 deletions packages/shared/comms/sagas.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
}
}
4 changes: 0 additions & 4 deletions packages/shared/dao/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@ export type SetCatalystCandidates = ReturnType<typeof setCatalystCandidates>
export const SELECT_NETWORK = '[DAO] Select network'
export const selectNetwork = (network: ETHEREUM_NETWORK) => action(SELECT_NETWORK, network)
export type SelectNetworkAction = ReturnType<typeof selectNetwork>

export const CATALYST_REALMS_SCAN_REQUESTED = '[Request] Catalyst Realms scan'
export const catalystRealmsScanRequested = () => action(CATALYST_REALMS_SCAN_REQUESTED)
export type CatalystRealmsScanRequested = ReturnType<typeof catalystRealmsScanRequested>
19 changes: 6 additions & 13 deletions packages/shared/dao/sagas.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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()}`)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
8 changes: 8 additions & 0 deletions packages/shared/realm/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 3 additions & 4 deletions packages/shared/scene-loader/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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, {
Expand Down
3 changes: 1 addition & 2 deletions packages/shared/voiceChat/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ 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'
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

Expand Down