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,31 @@
// @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 { values } from 'lib/utils/objects.js';
+import { promiseAll } from 'lib/utils/promises.js';
import {
usingCommServicesAccessToken,
createDefaultHTTPRequestHeaders,
@@ -27,6 +35,16 @@
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');
@@ -49,37 +67,24 @@
}),
);
- 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;
}
@@ -108,51 +113,187 @@
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 communities = values(filteredCommunitiesObj);
+
+ const communitesToJoin: CommunitiesToAutoJoin = {};
+
+ for (const community of communities) {
+ communitesToJoin[community.communityID] = community;
+ }
+
+ 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;
}