diff --git a/native/components/auto-join-community-handler.react.js b/native/components/auto-join-community-handler.react.js new file mode 100644 --- /dev/null +++ b/native/components/auto-join-community-handler.react.js @@ -0,0 +1,143 @@ +// @flow + +import * as React from 'react'; + +import { + joinThreadActionTypes, + useJoinThread, +} from 'lib/actions/thread-actions.js'; +import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; +import blobService from 'lib/facts/blob-service.js'; +import { cookieSelector } from 'lib/selectors/keyserver-selectors.js'; +import { farcasterChannelTagBlobHash } from 'lib/shared/community-utils.js'; +import { authoritativeKeyserverID } from 'lib/utils/authoritative-keyserver.js'; +import { getBlobFetchableURL } from 'lib/utils/blob-service.js'; +import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; +import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; + +import { nonThreadCalendarQuery } from '../navigation/nav-selectors.js'; +import { NavContext } from '../navigation/navigation-context.js'; +import { useSelector } from '../redux/redux-utils.js'; + +function AutoJoinCommunityHandler(): React.Node { + const isActive = useSelector(state => state.lifecycleState !== 'background'); + + const cookie = useSelector(cookieSelector(authoritativeKeyserverID())); + const hasUserCookie = !!(cookie && cookie.startsWith('user=')); + const currentUserID = useSelector(state => state.currentUserInfo?.id); + const loggedIn = !!currentUserID && hasUserCookie; + + const fid = useCurrentUserFID(); + + const neynarClient = React.useContext(NeynarClientContext)?.client; + + const navContext = React.useContext(NavContext); + + const calendarQuery = useSelector(state => + nonThreadCalendarQuery({ + redux: state, + navContext, + }), + ); + + const joinThread = useJoinThread(); + + const joinThreadActionPromise = React.useCallback( + async (communityID: string, channelID: string) => { + const query = calendarQuery(); + + return await joinThread({ + threadID: communityID, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [communityID] }, + ], + }, + farcasterChannelID: channelID, + }); + }, + [calendarQuery, joinThread], + ); + + const dispatchActionPromise = useDispatchActionPromise(); + + const threadInfos = useSelector(state => state.threadStore.threadInfos); + + React.useEffect(() => { + if (!loggedIn || !isActive || !fid || !neynarClient) { + return; + } + + void (async () => { + const followedFarcasterChannels = + await neynarClient.fetchFollowedFarcasterChannels(fid); + + const followedFarcasterChannelIDs = followedFarcasterChannels.map( + channel => channel.id, + ); + + const fetchBlobPromises = followedFarcasterChannelIDs.map(channelID => { + const blobHash = farcasterChannelTagBlobHash( + channelID, + authoritativeKeyserverID(), + ); + const blobURL = getBlobFetchableURL(blobHash); + + return fetch(blobURL, { + method: blobService.httpEndpoints.GET_BLOB.method, + }); + }); + + const fetchBlobResults = await Promise.all(fetchBlobPromises); + + const farcasterChannelTagBlobsPromises = fetchBlobResults + .map(blobResult => { + if (blobResult.status !== 200) { + return null; + } + + return blobResult.json(); + }) + .filter(Boolean); + + const farcasterChannelTagBlobs: $ReadOnlyArray<{ + +commCommunityID: string, + +farcasterChannelID: string, + }> = await Promise.all(farcasterChannelTagBlobsPromises); + + const joinThreadPromises = farcasterChannelTagBlobs + .map(blob => { + const threadID = `${authoritativeKeyserverID()}|${ + blob.commCommunityID + }`; + + if (threadInfos[threadID]) { + return null; + } + + return dispatchActionPromise( + joinThreadActionTypes, + joinThreadActionPromise(threadID, blob.farcasterChannelID), + ); + }) + .filter(Boolean); + + await Promise.all(joinThreadPromises); + })(); + }, [ + threadInfos, + dispatchActionPromise, + fid, + isActive, + joinThreadActionPromise, + loggedIn, + neynarClient, + ]); + + return null; +} + +export { AutoJoinCommunityHandler }; diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -46,6 +46,7 @@ import ChatContextProvider from './chat/chat-context-provider.react.js'; import MessageEditingContextProvider from './chat/message-editing-context-provider.react.js'; import AccessTokenHandler from './components/access-token-handler.react.js'; +import { AutoJoinCommunityHandler } from './components/auto-join-community-handler.react.js'; import ConnectFarcasterAlertHandler from './components/connect-farcaster-alert-handler.react.js'; import { FeatureFlagsProvider } from './components/feature-flags-provider.react.js'; import PersistedStateGate from './components/persisted-state-gate.js'; @@ -340,6 +341,7 @@ + {navigation}