diff --git a/native/components/auto-join-community-handler.react.js b/native/components/auto-join-community-handler.react.js --- a/native/components/auto-join-community-handler.react.js +++ b/native/components/auto-join-community-handler.react.js @@ -1,23 +1,29 @@ // @flow import invariant from 'invariant'; +import _pickBy from 'lodash/fp/pickBy.js'; 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 { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.js'; import { isLoggedInToIdentityAndAuthoritativeKeyserver } from 'lib/selectors/user-selectors.js'; -import { farcasterChannelTagBlobHash } from 'lib/shared/community-utils.js'; +import { + farcasterChannelTagBlobHash, + useJoinCommunity, +} from 'lib/shared/community-utils.js'; import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; +import type { KeyserverOverride } from 'lib/shared/invite-links.js'; +import type { + OngoingJoinCommunityData, + JoinCommunityStep, +} from 'lib/types/community-types.js'; import { defaultThreadSubscription } from 'lib/types/subscription-types.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 { values } from 'lib/utils/objects.js'; +import { promiseAll } from 'lib/utils/promises.js'; import { usingCommServicesAccessToken, createDefaultHTTPRequestHeaders, @@ -27,6 +33,11 @@ import { NavContext } from '../navigation/navigation-context.js'; import { useSelector } from '../redux/redux-utils.js'; +type CommunityToAutoJoin = { + +communityID: string, + +keyserverOverride: ?KeyserverOverride, +}; + function AutoJoinCommunityHandler(): React.Node { const isActive = useSelector(state => state.lifecycleState !== 'background'); @@ -36,49 +47,19 @@ const neynarClient = React.useContext(NeynarClientContext)?.client; - const navContext = React.useContext(NavContext); - const identityClientContext = React.useContext(IdentityClientContext); invariant(identityClientContext, 'IdentityClientContext should be set'); const { getAuthMetadata } = identityClientContext; - const calendarQuery = useSelector(state => - nonThreadCalendarQuery({ - redux: state, - navContext, - }), - ); - - const joinThread = useJoinThread(); - - const joinThreadActionPromise = React.useCallback( - async (communityID: string) => { - const query = calendarQuery(); - - return await joinThread({ - threadID: communityID, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [communityID] }, - ], - }, - defaultSubscription: defaultThreadSubscription, - }); - }, - [calendarQuery, joinThread], - ); - - const dispatchActionPromise = useDispatchActionPromise(); - const threadInfos = useSelector(state => state.threadStore.threadInfos); const keyserverInfos = useSelector( state => state.keyserverStore.keyserverInfos, ); + const [communitiesToAuoJoin, setCommunitiesToAutoJoin] = + React.useState>(); + React.useEffect(() => { if (!loggedIn || !isActive || !fid || !neynarClient || !threadInfos) { return; @@ -108,51 +89,119 @@ channel => channel.id, ); - const promises = followedFarcasterChannelIDs.map(async channelID => { - const blobHash = farcasterChannelTagBlobHash(channelID); - const blobURL = getBlobFetchableURL(blobHash); + const promises: { [string]: Promise } = {}; - const blobResult = await fetch(blobURL, { - method: blobService.httpEndpoints.GET_BLOB.method, - headers, - }); + for (const channelID of followedFarcasterChannelIDs) { + promises[channelID] = (async () => { + const blobHash = farcasterChannelTagBlobHash(channelID); + const blobURL = getBlobFetchableURL(blobHash); - if (blobResult.status !== 200) { - return; - } + const blobResult = await fetch(blobURL, { + method: blobService.httpEndpoints.GET_BLOB.method, + headers, + }); - const { commCommunityID } = await blobResult.json(); - const keyserverID = extractKeyserverIDFromID(commCommunityID); + if (blobResult.status !== 200) { + return null; + } - if (!keyserverInfos[keyserverID]) { - return; - } + const { commCommunityID, keyserverURL } = await blobResult.json(); + const keyserverID = extractKeyserverIDFromID(commCommunityID); - // The user is already in the community - if (threadInfos[commCommunityID]) { - return; - } + // The user is already in the community + if (threadInfos[commCommunityID]) { + return null; + } + + const keyserverOverride = !keyserverInfos[keyserverID] + ? { + keyserverID, + keyserverURL: keyserverURL.replace(/\/$/, ''), + } + : null; + + return { + communityID: commCommunityID, + keyserverOverride, + }; + })(); + } + + const communitiesObj = await promiseAll(promises); + + const filteredCommunitiesObj = _pickBy(Boolean)(communitiesObj); - void dispatchActionPromise( - joinThreadActionTypes, - joinThreadActionPromise(commCommunityID), - ); - }); + const communities = values(filteredCommunitiesObj); - await Promise.all(promises); + if (communities.length === 0) { + return; + } + + setCommunitiesToAutoJoin(communities); })(); }, [ threadInfos, - dispatchActionPromise, fid, isActive, - joinThreadActionPromise, loggedIn, neynarClient, getAuthMetadata, keyserverInfos, ]); + const joinHandlers = React.useMemo( + () => + communitiesToAuoJoin?.map((communityToAutoJoin, index) => ( + + )), + [communitiesToAuoJoin], + ); + + return joinHandlers; +} + +type JoinHandlerProps = { + +communityID: string, + +keyserverOverride: ?KeyserverOverride, +}; +function JoinHandler(props: JoinHandlerProps) { + const { communityID, keyserverOverride } = props; + + const navContext = React.useContext(NavContext); + + const calendarQuery = useSelector(state => + nonThreadCalendarQuery({ + redux: state, + navContext, + }), + ); + + const [ongoingJoinData, setOngoingJoinData] = + React.useState(null); + + const [step, setStep] = React.useState('inactive'); + + const joinCommunity = useJoinCommunity({ + communityID, + keyserverOverride, + calendarQuery, + ongoingJoinData, + setOngoingJoinData, + step, + setStep, + defaultSubscription: defaultThreadSubscription, + }); + + React.useEffect(() => { + void (async () => { + await joinCommunity(); + })(); + }, [joinCommunity]); + return null; }