diff --git a/native/components/auto-join-community-handler.react.js b/native/components/auto-join-community-handler.react.js
index 5061bd5d9..3cd2b0aa7 100644
--- a/native/components/auto-join-community-handler.react.js
+++ b/native/components/auto-join-community-handler.react.js
@@ -1,159 +1,298 @@
// @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 { useIsLoggedInToIdentityAndAuthoritativeKeyserver } from 'lib/hooks/account-hooks.js';
import { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.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 type { CalendarQuery } from 'lib/types/entry-types.js';
+import type { SetState } from 'lib/types/hook-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 { promiseAll } from 'lib/utils/promises.js';
import {
usingCommServicesAccessToken,
createDefaultHTTPRequestHeaders,
} from 'lib/utils/services-utils.js';
import { nonThreadCalendarQuery } from '../navigation/nav-selectors.js';
import { NavContext } from '../navigation/navigation-context.js';
import { useSelector } from '../redux/redux-utils.js';
+type CommunityToAutoJoin = {
+ +communityID: string,
+ +keyserverOverride: ?KeyserverOverride,
+ +joinStatus: 'inactive' | 'joining' | 'joined',
+};
+
+type CommunitiesToAutoJoin = {
+ +[communityID: string]: CommunityToAutoJoin,
+};
+
function AutoJoinCommunityHandler(): React.Node {
const isActive = useSelector(state => state.lifecycleState !== 'background');
const loggedIn = useIsLoggedInToIdentityAndAuthoritativeKeyserver();
const fid = useCurrentUserFID();
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 [communitiesToAutoJoin, setCommunitiesToAutoJoin] =
+ React.useState();
+
+ const prevCanQueryRef = React.useRef();
+ const canQuery = loggedIn;
+
React.useEffect(() => {
+ if (canQuery === prevCanQueryRef.current) {
+ return;
+ }
+
+ prevCanQueryRef.current = canQuery;
if (!loggedIn || !isActive || !fid || !neynarClient || !threadInfos) {
return;
}
void (async () => {
const authMetadataPromise: Promise = (async () => {
if (!usingCommServicesAccessToken) {
return undefined;
}
return await getAuthMetadata();
})();
const followedFarcasterChannelsPromise =
neynarClient.fetchFollowedFarcasterChannels(fid);
const [authMetadata, followedFarcasterChannels] = await Promise.all([
authMetadataPromise,
followedFarcasterChannelsPromise,
]);
const headers = authMetadata
? createDefaultHTTPRequestHeaders(authMetadata)
: {};
const followedFarcasterChannelIDs = followedFarcasterChannels.map(
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,
+ joinStatus: 'inactive',
+ };
+ })();
+ }
+
+ const communitiesObj = await promiseAll(promises);
- void dispatchActionPromise(
- joinThreadActionTypes,
- joinThreadActionPromise(commCommunityID),
- );
- });
+ const filteredCommunitiesObj = _pickBy(Boolean)(communitiesObj);
- await Promise.all(promises);
+ const communitesToJoin: { ...CommunitiesToAutoJoin } = {};
+
+ for (const key in filteredCommunitiesObj) {
+ const communityID = filteredCommunitiesObj[key].communityID;
+ communitesToJoin[communityID] = filteredCommunitiesObj[key];
+ }
+
+ setCommunitiesToAutoJoin(communitesToJoin);
})();
}, [
threadInfos,
- dispatchActionPromise,
fid,
isActive,
- joinThreadActionPromise,
loggedIn,
neynarClient,
getAuthMetadata,
keyserverInfos,
+ canQuery,
]);
+ const joinHandlers = React.useMemo(() => {
+ if (!communitiesToAutoJoin) {
+ return null;
+ }
+
+ return Object.keys(communitiesToAutoJoin).map(id => {
+ const communityToAutoJoin = communitiesToAutoJoin[id];
+
+ const { communityID, keyserverOverride, joinStatus } =
+ communityToAutoJoin;
+
+ if (joinStatus === 'joined') {
+ return null;
+ }
+
+ return (
+
+ );
+ });
+ }, [calendarQuery, communitiesToAutoJoin]);
+
+ return joinHandlers;
+}
+
+type JoinHandlerProps = {
+ +communityID: string,
+ +keyserverOverride: ?KeyserverOverride,
+ +calendarQuery: () => CalendarQuery,
+ +communitiesToAutoJoin: CommunitiesToAutoJoin,
+ +setCommunitiesToAutoJoin: SetState,
+};
+function JoinHandler(props: JoinHandlerProps) {
+ const {
+ communityID,
+ keyserverOverride,
+ calendarQuery,
+ communitiesToAutoJoin,
+ setCommunitiesToAutoJoin,
+ } = props;
+
+ 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(() => {
+ const joinStatus = communitiesToAutoJoin[communityID]?.joinStatus;
+ if (joinStatus !== 'inactive') {
+ return;
+ }
+
+ void joinCommunity();
+ }, [
+ communitiesToAutoJoin,
+ communityID,
+ joinCommunity,
+ setCommunitiesToAutoJoin,
+ ]);
+
+ React.useEffect(() => {
+ if (step !== 'add_keyserver') {
+ return;
+ }
+
+ setCommunitiesToAutoJoin(prev => {
+ if (!prev) {
+ return null;
+ }
+
+ return {
+ ...prev,
+ [communityID]: {
+ ...prev[communityID],
+ joinStatus: 'joining',
+ },
+ };
+ });
+ }, [communityID, setCommunitiesToAutoJoin, step]);
+
+ React.useEffect(() => {
+ if (step !== 'finished') {
+ return;
+ }
+
+ setCommunitiesToAutoJoin(prev => {
+ if (!prev) {
+ return null;
+ }
+
+ return {
+ ...prev,
+ [communityID]: {
+ ...prev[communityID],
+ joinStatus: 'joined',
+ },
+ };
+ });
+ }, [communityID, step, setCommunitiesToAutoJoin]);
+
return null;
}
export { AutoJoinCommunityHandler };