From 92ab4a03d8a3f56852d9ea37b86a016d2c292c29 Mon Sep 17 00:00:00 2001 From: Andrew Mendelsohn Date: Fri, 7 Oct 2022 18:14:25 -0400 Subject: [PATCH] [C-1184] Fetch lineups after coming back online (#2083) --- .../common/src/store/reachability/reducer.ts | 2 +- packages/mobile/src/hooks/useReachability.ts | 43 +++++++++++++++++++ .../src/screens/root-screen/RootScreen.tsx | 20 ++++++--- .../web/src/common/store/backend/sagas.ts | 42 ++++-------------- .../web/src/common/store/backend/selectors.ts | 2 + packages/web/src/common/store/lineup/sagas.js | 11 ++++- .../store/pages/profile/lineups/feed/sagas.js | 40 ++++++++--------- .../src/common/store/reachability/sagas.ts | 19 ++++++++ 8 files changed, 117 insertions(+), 62 deletions(-) create mode 100644 packages/mobile/src/hooks/useReachability.ts create mode 100644 packages/web/src/common/store/reachability/sagas.ts diff --git a/packages/common/src/store/reachability/reducer.ts b/packages/common/src/store/reachability/reducer.ts index eccad41f95..ead4321625 100644 --- a/packages/common/src/store/reachability/reducer.ts +++ b/packages/common/src/store/reachability/reducer.ts @@ -6,7 +6,7 @@ import { ReachabilityState } from './types' type ReachabilityActions = ActionType const initialState = { - networkReachable: true + networkReachable: false } const reducer = createReducer( diff --git a/packages/mobile/src/hooks/useReachability.ts b/packages/mobile/src/hooks/useReachability.ts new file mode 100644 index 0000000000..a0cfa953ec --- /dev/null +++ b/packages/mobile/src/hooks/useReachability.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react' + +import { reachabilitySelectors } from '@audius/common' +import { useSelector } from 'react-redux' +const { getIsReachable } = reachabilitySelectors + +type OnBecomeReachable = () => any +type OnBecomeUnreachable = () => any + +export const useReachability = ( + onBecomeReachable: OnBecomeReachable | null, + onBecomeUnreachable: OnBecomeUnreachable | null +) => { + const isInternetReachable = useSelector(getIsReachable) + const [lastReachabilityState, setLastReachabilityState] = + useState(isInternetReachable) + + useEffect(() => { + if (isInternetReachable !== lastReachabilityState) { + isInternetReachable ? onBecomeReachable?.() : onBecomeUnreachable?.() + setLastReachabilityState(isInternetReachable) + } + }, [ + isInternetReachable, + lastReachabilityState, + onBecomeReachable, + onBecomeUnreachable + ]) + + return isInternetReachable +} + +export const useBecomeReachable = (onBecomeReachable: OnBecomeReachable) => { + return useReachability(onBecomeReachable, null) +} + +export const useBecomeUnreachable = ( + onBecomeUnreachable: OnBecomeUnreachable +) => { + return useReachability(null, onBecomeUnreachable) +} + +export default useReachability diff --git a/packages/mobile/src/screens/root-screen/RootScreen.tsx b/packages/mobile/src/screens/root-screen/RootScreen.tsx index 794429471e..2120f36afd 100644 --- a/packages/mobile/src/screens/root-screen/RootScreen.tsx +++ b/packages/mobile/src/screens/root-screen/RootScreen.tsx @@ -1,12 +1,14 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { accountSelectors, Status } from '@audius/common' import type { NavigatorScreenParams } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' import { setupBackend } from 'audius-client/src/common/store/backend/actions' +import { getIsSetup } from 'audius-client/src/common/store/backend/selectors' import { useDispatch, useSelector } from 'react-redux' import useAppState from 'app/hooks/useAppState' +import { useBecomeReachable } from 'app/hooks/useReachability' import { useUpdateRequired } from 'app/hooks/useUpdateRequired' import type { AppScreenParamList } from 'app/screens/app-screen' import { SignOnScreen } from 'app/screens/signon' @@ -41,13 +43,15 @@ export const RootScreen = ({ isReadyToSetupBackend }: RootScreenProps) => { const accountStatus = useSelector(getAccountStatus) const [isInitting, setIsInittng] = useState(true) const { updateRequired } = useUpdateRequired() + const isBackendSetup = useSelector(getIsSetup) + + const handleSetupBackend = useCallback(() => { + if (isReadyToSetupBackend && !isBackendSetup) dispatch(setupBackend()) + }, [dispatch, isBackendSetup, isReadyToSetupBackend]) useEffect(() => { - // Setup the backend when ready - if (isReadyToSetupBackend) { - dispatch(setupBackend()) - } - }, [dispatch, isReadyToSetupBackend]) + handleSetupBackend() + }, [dispatch, handleSetupBackend, isBackendSetup, isReadyToSetupBackend]) useAppState( () => dispatch(enterForeground()), @@ -60,6 +64,10 @@ export const RootScreen = ({ isReadyToSetupBackend }: RootScreenProps) => { } }, [accountStatus]) + useBecomeReachable(() => { + handleSetupBackend() + }) + return ( <> diff --git a/packages/web/src/common/store/backend/sagas.ts b/packages/web/src/common/store/backend/sagas.ts index 61b51cf57d..4427ec06bb 100644 --- a/packages/web/src/common/store/backend/sagas.ts +++ b/packages/web/src/common/store/backend/sagas.ts @@ -4,23 +4,14 @@ import { reachabilitySelectors, getContext } from '@audius/common' -import { - put, - all, - delay, - take, - takeEvery, - select, - call, - race -} from 'typed-redux-saga' +import { put, all, take, takeEvery, select, call } from 'typed-redux-saga' + +import { getReachability } from '../reachability/sagas' import * as backendActions from './actions' import { watchBackendErrors } from './errorSagas' const { getIsReachable } = reachabilitySelectors -const REACHABILITY_TIMEOUT_MS = 8 * 1000 - /** * Waits for the backend to be setup. Can be used as a blocking call in another saga, * For example: @@ -44,28 +35,7 @@ export function* waitForBackendSetup() { } } -function* awaitReachability() { - const isNativeMobile = yield* getContext('isNativeMobile') - const isReachable = yield* select(getIsReachable) - if (isReachable || !isNativeMobile) return true - const { action } = yield* race({ - action: take(reachabilityActions.SET_REACHABLE), - delay: delay(REACHABILITY_TIMEOUT_MS) - }) - return !!action -} - export function* setupBackend() { - const establishedReachability = yield* call(awaitReachability) - // If we couldn't connect, show the error page - // and just sit here waiting for reachability. - if (!establishedReachability) { - console.error('No internet connectivity') - yield* put(accountActions.fetchAccountNoInternet()) - yield* take(reachabilityActions.SET_REACHABLE) - console.info('Reconnected') - } - const apiClient = yield* getContext('apiClient') const fingerprintClient = yield* getContext('fingerprintClient') const audiusBackendInstance = yield* getContext('audiusBackendInstance') @@ -74,7 +44,13 @@ export function* setupBackend() { apiClient.init() // Fire-and-forget init fp fingerprintClient.init() + const isReachable = yield* call(getReachability) yield* put(accountActions.fetchAccount()) + + if (!isReachable) { + yield* put(accountActions.fetchAccountNoInternet()) + } + const { web3Error, libsError } = yield* call(audiusBackendInstance.setup) if (libsError) { yield* put(accountActions.fetchAccountFailed({ reason: 'LIBS_ERROR' })) diff --git a/packages/web/src/common/store/backend/selectors.ts b/packages/web/src/common/store/backend/selectors.ts index 80545f104d..f05342bde7 100644 --- a/packages/web/src/common/store/backend/selectors.ts +++ b/packages/web/src/common/store/backend/selectors.ts @@ -2,3 +2,5 @@ import { AppState } from 'store/types' export const getWeb3Error = (state: AppState) => state.backend.web3Error + +export const getIsSetup = (state: AppState) => state.backend.isSetup diff --git a/packages/web/src/common/store/lineup/sagas.js b/packages/web/src/common/store/lineup/sagas.js index 1274143500..e7325e87cc 100644 --- a/packages/web/src/common/store/lineup/sagas.js +++ b/packages/web/src/common/store/lineup/sagas.js @@ -29,6 +29,8 @@ import { import { getToQueue } from 'common/store/queue/sagas' import { isMobileWeb } from 'common/utils/isMobileWeb' +import { awaitReachability } from '../reachability/sagas' + const { getSource, getUid, getPositions } = queueSelectors const { getUid: getCurrentPlayerTrackUid } = playerSelectors const { getUsers } = cacheUsersSelectors @@ -117,6 +119,8 @@ function* fetchLineupMetadatasAsync( action ) { const initLineup = yield select(lineupSelector) + yield call(awaitReachability) + const initSource = sourceSelector ? yield select((state) => sourceSelector(state, action.handle?.toLowerCase()) @@ -137,14 +141,17 @@ function* fetchLineupMetadatasAsync( // Let page animations on mobile have time to breathe // TODO: Get rid of this once we figure out how to make loading better - const isNativeMobile = yield getContext('isNativeMobile') + const isNativeMobile = yield* getContext('isNativeMobile') if (!isNativeMobile && isMobileWeb()) { yield delay(100) } const lineupMetadatasResponse = yield call(lineupMetadatasCall, action) - if (lineupMetadatasResponse === null) return + if (lineupMetadatasResponse === null) { + yield put(lineupActions.fetchLineupMetadatasFailed()) + return + } const lineup = yield select((state) => lineupSelector(state, action.handle?.toLowerCase()) ) diff --git a/packages/web/src/common/store/pages/profile/lineups/feed/sagas.js b/packages/web/src/common/store/pages/profile/lineups/feed/sagas.js index e79f0926b7..a4561b3007 100644 --- a/packages/web/src/common/store/pages/profile/lineups/feed/sagas.js +++ b/packages/web/src/common/store/pages/profile/lineups/feed/sagas.js @@ -12,7 +12,7 @@ import { waitForAccount, makeUid } from '@audius/common' -import { select, call, takeEvery, put } from 'redux-saga/effects' +import { select, call, takeEvery, put } from 'typed-redux-saga' import { getConfirmCalls } from 'common/store/confirmer/selectors' import { LineupSagas } from 'common/store/lineup/sagas' @@ -24,11 +24,11 @@ const { getCollections } = cacheCollectionsSelectors const { getUserId, getUserHandle } = accountSelectors function* getReposts({ offset, limit, handle }) { - const profileId = yield select((state) => getProfileUserId(state, handle)) + const profileId = yield* select((state) => getProfileUserId(state, handle)) yield waitForAccount() - const currentUserId = yield select(getUserId) - let reposts = yield call(retrieveUserReposts, { + const currentUserId = yield* select(getUserId) + let reposts = yield* call(retrieveUserReposts, { handle, currentUserId, offset, @@ -40,7 +40,7 @@ function* getReposts({ offset, limit, handle }) { // Only do this on page 1 of the reposts tab if (profileId === currentUserId && offset === 0) { // Get everything that is confirming - const confirming = yield select(getConfirmCalls) + const confirming = yield* select(getConfirmCalls) if (Object.keys(confirming).length > 0) { const repostTrackIds = new Set( reposts.map((r) => r.track_id).filter(Boolean) @@ -49,8 +49,8 @@ function* getReposts({ offset, limit, handle }) { reposts.map((r) => r.playlist_id).filter(Boolean) ) - const tracks = yield select(getTracks) - const collections = yield select(getCollections) + const tracks = yield* select(getTracks) + const collections = yield* select(getCollections) // For each confirming entry, check if it's a track or collection, // then check if we have reposted/favorited it, and check to make @@ -103,7 +103,7 @@ class FeedSagas extends LineupSagas { function* addTrackRepost(action) { const { trackId, source } = action - const accountHandle = yield select(getUserHandle) + const accountHandle = yield* select(getUserHandle) const formattedTrack = { kind: Kind.TRACKS, @@ -111,34 +111,34 @@ function* addTrackRepost(action) { uid: makeUid(Kind.TRACKS, trackId, source) } - yield put(feedActions.add(formattedTrack, trackId, accountHandle, true)) + yield* put(feedActions.add(formattedTrack, trackId, accountHandle, true)) } function* watchRepostTrack() { - yield takeEvery(tracksSocialActions.REPOST_TRACK, addTrackRepost) + yield* takeEvery(tracksSocialActions.REPOST_TRACK, addTrackRepost) } function* removeTrackRepost(action) { const { trackId } = action - const accountHandle = yield select(getUserHandle) - const lineup = yield select((state) => + const accountHandle = yield* select(getUserHandle) + const lineup = yield* select((state) => getProfileFeedLineup(state, accountHandle) ) const trackLineupEntry = lineup.entries.find((entry) => entry.id === trackId) if (trackLineupEntry) { - yield put( + yield* put( feedActions.remove(Kind.TRACKS, trackLineupEntry.uid, accountHandle) ) } } function* watchUndoRepostTrack() { - yield takeEvery(tracksSocialActions.UNDO_REPOST_TRACK, removeTrackRepost) + yield* takeEvery(tracksSocialActions.UNDO_REPOST_TRACK, removeTrackRepost) } function* addCollectionRepost(action) { const { collectionId, source } = action - const accountHandle = yield select(getUserHandle) + const accountHandle = yield* select(getUserHandle) const formattedCollection = { kind: Kind.COLLECTIONS, @@ -152,7 +152,7 @@ function* addCollectionRepost(action) { } function* watchRepostCollection() { - yield takeEvery( + yield* takeEvery( collectionsSocialActions.REPOST_COLLECTION, addCollectionRepost ) @@ -160,15 +160,15 @@ function* watchRepostCollection() { function* removeCollectionRepost(action) { const { collectionId } = action - const accountHandle = yield select(getUserHandle) - const lineup = yield select((state) => + const accountHandle = yield* select(getUserHandle) + const lineup = yield* select((state) => getProfileFeedLineup(state, accountHandle) ) const collectionLineupEntry = lineup.entries.find( (entry) => entry.id === collectionId ) if (collectionLineupEntry) { - yield put( + yield* put( feedActions.remove( Kind.COLLECTIONS, collectionLineupEntry.uid, @@ -179,7 +179,7 @@ function* removeCollectionRepost(action) { } function* watchUndoRepostCollection() { - yield takeEvery( + yield* takeEvery( collectionsSocialActions.UNDO_REPOST_COLLECTION, removeCollectionRepost ) diff --git a/packages/web/src/common/store/reachability/sagas.ts b/packages/web/src/common/store/reachability/sagas.ts new file mode 100644 index 0000000000..bcbdf47a9a --- /dev/null +++ b/packages/web/src/common/store/reachability/sagas.ts @@ -0,0 +1,19 @@ +import { + reachabilityActions, + reachabilitySelectors, + getContext +} from '@audius/common' +import { take, select, call } from 'typed-redux-saga' + +const { getIsReachable } = reachabilitySelectors + +export function* getReachability() { + return yield* select(getIsReachable) +} + +export function* awaitReachability() { + const isNativeMobile = yield* getContext('isNativeMobile') + const isReachable = yield* call(getReachability) + if (isReachable || !isNativeMobile) return + yield* take(reachabilityActions.SET_REACHABLE) +}