Page MenuHomePhorge

D15346.1765051422.diff
No OneTemporary

Size
45 KB
Referenced Files
None
Subscribers
None

D15346.1765051422.diff

diff --git a/lib/contexts/protocol-selection-context.js b/lib/contexts/protocol-selection-context.js
new file mode 100644
--- /dev/null
+++ b/lib/contexts/protocol-selection-context.js
@@ -0,0 +1,30 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import type { ProtocolName } from '../shared/threads/thread-spec.js';
+import type { AccountUserInfo } from '../types/user-types.js';
+
+type ProtocolSelectionContextType = {
+ +selectedProtocol: ?ProtocolName,
+ +setSelectedProtocol: (?ProtocolName) => mixed,
+ +availableProtocols: $ReadOnlyArray<ProtocolName>,
+ +setUserInfoInput?: ($ReadOnlyArray<AccountUserInfo>) => mixed,
+ +setSearching?: boolean => mixed,
+};
+
+const ProtocolSelectionContext: React.Context<?ProtocolSelectionContextType> =
+ React.createContext<?ProtocolSelectionContextType>(null);
+
+function useProtocolSelection(): ProtocolSelectionContextType {
+ const context = React.useContext(ProtocolSelectionContext);
+ invariant(
+ context,
+ 'useProtocolSelection must be used within a ProtocolSelectionProvider',
+ );
+ return context;
+}
+
+export type { ProtocolSelectionContextType };
+export { ProtocolSelectionContext, useProtocolSelection };
diff --git a/lib/hooks/user-identities-hooks.js b/lib/hooks/user-identities-hooks.js
--- a/lib/hooks/user-identities-hooks.js
+++ b/lib/hooks/user-identities-hooks.js
@@ -9,6 +9,7 @@
import type { FarcasterUser } from '../types/identity-service-types.js';
import type { AccountUserInfo } from '../types/user-types.js';
import { useSelector } from '../utils/redux-utils.js';
+import { useIsFarcasterDCsIntegrationEnabled } from '../utils/services-utils.js';
function useUsersSupportThickThreads(): (
userIDs: $ReadOnlyArray<string>,
@@ -54,11 +55,19 @@
) => Promise<$ReadOnlyMap<string, boolean | void>> {
const findUserIdentities = useFindUserIdentities();
const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos);
+ const supportsFarcasterDCs = useIsFarcasterDCsIntegrationEnabled();
return React.useCallback(
async (userIDs: $ReadOnlyArray<string>) => {
const usersSupportingFCDCs = new Map<string, boolean | void>();
+ if (!supportsFarcasterDCs) {
+ for (const userID of userIDs) {
+ usersSupportingFCDCs.set(userID, false);
+ }
+ return usersSupportingFCDCs;
+ }
+
const usersNeedingFetch = [];
for (const userID of userIDs) {
const userIDIsFarcasterFID = !!extractFIDFromUserID(userID);
@@ -91,7 +100,7 @@
}
return usersSupportingFCDCs;
},
- [auxUserInfos, findUserIdentities],
+ [auxUserInfos, findUserIdentities, supportsFarcasterDCs],
);
}
@@ -160,7 +169,8 @@
const unresolvedFIDs = new Set(farcasterIDs.map(fid => `${fid}`));
for (const [userID, auxUserInfo] of Object.entries(auxUserInfos)) {
- const { fid, supportsFarcasterDCs } = auxUserInfo;
+ const { fid, supportsFarcasterDCs: userSupportsFarcasterDCs } =
+ auxUserInfo;
const username = userInfos[userID]?.username;
if (fid && username && unresolvedFIDs.delete(fid)) {
@@ -168,7 +178,7 @@
userID,
farcasterID: fid,
username,
- supportsFarcasterDCs: !!supportsFarcasterDCs,
+ supportsFarcasterDCs: !!userSupportsFarcasterDCs,
});
}
}
diff --git a/lib/selectors/thread-selectors.js b/lib/selectors/thread-selectors.js
--- a/lib/selectors/thread-selectors.js
+++ b/lib/selectors/thread-selectors.js
@@ -487,19 +487,27 @@
];
}
- for (const type of possiblePendingThreadTypes) {
- const pendingThreadID = getPendingThreadID(
- type,
- actualMemberIDs,
- rawThreadInfo.sourceMessageID,
- );
- const existingResult = result.get(pendingThreadID);
- if (
- !existingResult ||
- rawThreadInfos[existingResult].creationTime >
- rawThreadInfo.creationTime
- ) {
- result.set(pendingThreadID, threadID);
+ const possibleProtocols = [
+ threadSpecs[rawThreadInfo.type].protocol(),
+ null,
+ ];
+
+ for (const protocol of possibleProtocols) {
+ for (const type of possiblePendingThreadTypes) {
+ const pendingThreadID = getPendingThreadID(
+ type,
+ actualMemberIDs,
+ rawThreadInfo.sourceMessageID,
+ protocol ? protocol.protocolName : null,
+ );
+ const existingResult = result.get(pendingThreadID);
+ if (
+ !existingResult ||
+ rawThreadInfos[existingResult].creationTime >
+ rawThreadInfo.creationTime
+ ) {
+ result.set(pendingThreadID, threadID);
+ }
}
}
}
diff --git a/lib/shared/search-utils.js b/lib/shared/search-utils.js
--- a/lib/shared/search-utils.js
+++ b/lib/shared/search-utils.js
@@ -4,12 +4,14 @@
import { useGetFarcasterDirectCastUsers } from './farcaster/farcaster-api.js';
import { messageID, userIDFromFID, extractFIDFromUserID } from './id-utils.js';
+import { protocolNames } from './protocol-names.js';
import SearchIndex from './search-index.js';
import {
getContainingThreadID,
userIsMember,
userHasDeviceList,
} from './thread-utils.js';
+import type { ProtocolName } from './threads/thread-spec.js';
import { threadSpecs, threadTypeIsSidebar } from './threads/thread-specs.js';
import { ensNameForFarcasterUsername } from './user-utils.js';
import { searchMessagesActionTypes } from '../actions/message-actions.js';
@@ -18,8 +20,13 @@
searchUsersActionTypes,
} from '../actions/user-actions.js';
import { ENSCacheContext } from '../components/ens-cache-provider.react.js';
+import { useProtocolSelection } from '../contexts/protocol-selection-context.js';
import genesis from '../facts/genesis.js';
import { useSearchMessages as useSearchMessagesAction } from '../hooks/message-hooks.js';
+import {
+ useUsersSupportFarcasterDCs,
+ useUsersSupportThickThreads,
+} from '../hooks/user-identities-hooks.js';
import { useIdentitySearch } from '../identity-search/identity-search-context.js';
import { useLegacyAshoatKeyserverCall } from '../keyserver-conn/legacy-keyserver-call.js';
import { decodeThreadRolePermissionsBitmaskArray } from '../permissions/minimally-encoded-thread-permissions.js';
@@ -126,6 +133,7 @@
}): UserListItem[] {
const memoizedUserInfos = React.useMemo(() => values(userInfos), [userInfos]);
const searchIndex: SearchIndex = useUserSearchIndex(memoizedUserInfos);
+ const { selectedProtocol } = useProtocolSelection();
const communityThreadInfo = React.useMemo(
() =>
@@ -246,7 +254,10 @@
]);
const viewerID = useSelector(state => state.currentUserInfo?.id);
- const sortedMembers = React.useMemo(() => {
+ const [sortedMembers, setSortedMembers] = React.useState<
+ Omit<UserListItem, 'supportedProtocols'>[],
+ >([]);
+ React.useEffect(() => {
const nonFriends = [];
const blockedUsers = [];
const friends = [];
@@ -277,7 +288,7 @@
.concat(nonFriends)
.concat(blockedUsers);
- return sortedResults.map(
+ const mappedResult = sortedResults.map(
({
isMemberOfContainingThread,
isMemberOfParentThread,
@@ -340,16 +351,61 @@
return result;
},
);
+ setSortedMembers(mappedResult);
}, [
containingThreadInfo,
filteredUserResults,
parentThreadInfo,
threadType,
viewerID,
+ auxUserInfos,
isFarcasterDCsIntegrationEnabled,
]);
- return sortedMembers;
+ const usersSupportFarcasterDCs = useUsersSupportFarcasterDCs();
+ const usersSupportThickThreads = useUsersSupportThickThreads();
+
+ const [potentialMembers, setPotentialMembers] = React.useState<
+ UserListItem[],
+ >([]);
+ React.useEffect(() => {
+ void (async () => {
+ const usersIDs = sortedMembers.map(user => user.id);
+ const [thickThreadUsers, farcasterUsers] = await Promise.all([
+ usersSupportThickThreads(usersIDs),
+ usersSupportFarcasterDCs(usersIDs),
+ ]);
+ const usersWithProtocol = sortedMembers
+ .map(member => {
+ const supportedProtocols: Array<ProtocolName> = [];
+ if (thickThreadUsers.get(member.id)) {
+ supportedProtocols.push(protocolNames.COMM_DM);
+ }
+ if (farcasterUsers.get(member.id)) {
+ supportedProtocols.push(protocolNames.FARCASTER_DC);
+ }
+ if (
+ selectedProtocol &&
+ !supportedProtocols.includes(selectedProtocol)
+ ) {
+ return null;
+ }
+ return {
+ ...member,
+ supportedProtocols: [...supportedProtocols],
+ };
+ })
+ .filter(Boolean);
+ setPotentialMembers(usersWithProtocol);
+ })();
+ }, [
+ selectedProtocol,
+ sortedMembers,
+ usersSupportFarcasterDCs,
+ usersSupportThickThreads,
+ ]);
+
+ return potentialMembers;
}
function useSearchMessages(): (
diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -13,10 +13,11 @@
import { relationshipBlockedInEitherDirection } from './relationship-utils.js';
import type { SidebarItem } from './sidebar-item-utils.js';
import { dmThreadProtocol } from './threads/protocols/dm-thread-protocol.js';
-import { farcasterThreadProtocol } from './threads/protocols/farcaster-thread-protocol.js';
-import { keyserverThreadProtocol } from './threads/protocols/keyserver-thread-protocol.js';
-import { protocols } from './threads/protocols/thread-protocols.js';
-import type { ThreadProtocol } from './threads/thread-spec.js';
+import {
+ getProtocolByName,
+ protocols,
+} from './threads/protocols/thread-protocols.js';
+import type { ProtocolName } from './threads/thread-spec.js';
import {
threadSpecs,
threadTypeIsCommunityRoot,
@@ -116,7 +117,6 @@
import { entries, values } from '../utils/objects.js';
import { useSelector } from '../utils/redux-utils.js';
import { userSurfacedPermissionsFromRolePermissions } from '../utils/role-utils.js';
-import { useIsFarcasterDCsIntegrationEnabled } from '../utils/services-utils.js';
import { firstLine } from '../utils/string-utils.js';
import {
pendingThreadIDRegex,
@@ -393,6 +393,7 @@
threadType: ThreadType,
memberIDs: $ReadOnlyArray<string>,
sourceMessageID: ?string,
+ protocol?: ?ProtocolName,
): string {
let pendingThreadKey;
if (sourceMessageID) {
@@ -405,13 +406,15 @@
}
const pendingThreadTypeString = sourceMessageID ? '' : `type${threadType}/`;
- return `pending/${pendingThreadTypeString}${pendingThreadKey}`;
+ const pendingThreadProtocolString = protocol ? `/protocol_${protocol}` : '';
+ return `pending/${pendingThreadTypeString}${pendingThreadKey}${pendingThreadProtocolString}`;
}
type PendingThreadIDContents = {
+threadType: ThreadType,
+memberIDs: $ReadOnlyArray<string>,
+sourceMessageID: ?string,
+ +protocol: ?string,
};
function parsePendingThreadID(
@@ -424,12 +427,20 @@
}
const [threadTypeString, threadKey] = pendingThreadIDMatches[1].split('/');
+ const protocolString =
+ pendingThreadIDMatches[pendingThreadIDMatches.length - 1];
+
const threadType =
protocols().find(
p => p.sidebarConfig?.pendingSidebarURLPrefix === threadTypeString,
)?.sidebarConfig?.sidebarThreadType ??
assertThreadType(Number(threadTypeString.replace('type', '')));
+ let protocol = null;
+ if (protocolString && protocolString.startsWith('/protocol_')) {
+ protocol = protocolString.replace('/protocol_', '');
+ }
+
const threadTypeStringIsSidebar = threadTypeIsSidebar(threadType);
const memberIDs = threadTypeStringIsSidebar ? [] : threadKey.split('+');
const sourceMessageID = threadTypeStringIsSidebar ? threadKey : null;
@@ -437,6 +448,7 @@
threadType,
memberIDs,
sourceMessageID,
+ protocol,
};
}
@@ -465,7 +477,12 @@
}
const memberIDs = members.map(member => member.id);
- const threadID = getPendingThreadID(threadType, memberIDs, sourceMessageID);
+ const threadID = getPendingThreadID(
+ threadType,
+ memberIDs,
+ sourceMessageID,
+ threadSpecs[threadType].protocol().protocolName,
+ );
const permissions: ThreadRolePermissionsBlob = {
[threadPermissions.KNOW_OF]: true,
@@ -1122,8 +1139,6 @@
type ExistingThreadInfoFinderParams = {
+searching: boolean,
+userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
- +allUsersSupportThickThreads: boolean,
- +allUsersSupportFarcasterThreads: boolean,
};
type ExistingThreadInfoFinder = (
params: ExistingThreadInfoFinderParams,
@@ -1131,9 +1146,8 @@
function useExistingThreadInfoFinder(
baseThreadInfo: ?ThreadInfo,
+ selectedProtocol: ?ProtocolName,
): ExistingThreadInfoFinder {
- const isFarcasterDCsIntegrationEnabled =
- useIsFarcasterDCsIntegrationEnabled();
const threadInfos = useSelector(threadInfoSelector);
const loggedInUserInfo = useLoggedInUserInfo();
@@ -1166,12 +1180,15 @@
const { sourceMessageID } = baseThreadInfo;
+ const protocol = getProtocolByName(selectedProtocol) ?? dmThreadProtocol;
+
let pendingThreadID;
if (searching) {
pendingThreadID = getPendingThreadID(
- dmThreadProtocol.pendingThreadType(userInfoInputArray.length),
+ protocol.pendingThreadType(userInfoInputArray.length),
[...userInfoInputArray.map(user => user.id), viewerID],
sourceMessageID,
+ selectedProtocol,
);
} else {
pendingThreadID = getPendingThreadID(
@@ -1189,16 +1206,6 @@
return baseThreadInfo;
}
- let protocol: ThreadProtocol<any> = keyserverThreadProtocol;
- if (
- params.allUsersSupportFarcasterThreads &&
- isFarcasterDCsIntegrationEnabled
- ) {
- protocol = farcasterThreadProtocol;
- } else if (params.allUsersSupportThickThreads) {
- protocol = dmThreadProtocol;
- }
-
return createPendingThread({
viewerID,
threadType: protocol.pendingThreadType(userInfoInputArray.length),
@@ -1213,7 +1220,7 @@
threadInfos,
loggedInUserInfo,
pendingToRealizedThreadIDs,
- isFarcasterDCsIntegrationEnabled,
+ selectedProtocol,
],
);
}
diff --git a/lib/shared/thread-utils.test.js b/lib/shared/thread-utils.test.js
--- a/lib/shared/thread-utils.test.js
+++ b/lib/shared/thread-utils.test.js
@@ -17,6 +17,7 @@
threadType: threadTypes.SIDEBAR,
memberIDs: [],
sourceMessageID: '12345',
+ protocol: null,
};
expect(parsePendingThreadID('pending/sidebar/12345')).toStrictEqual(
sidebarResult,
@@ -26,6 +27,7 @@
threadType: threadTypes.SIDEBAR,
memberIDs: [],
sourceMessageID: '789|12345',
+ protocol: null,
};
expect(parsePendingThreadID('pending/sidebar/789|12345')).toStrictEqual(
sidebarResultWithNewSchema,
@@ -36,6 +38,7 @@
threadType: threadTypes.THICK_SIDEBAR,
memberIDs: [],
sourceMessageID: '12345',
+ protocol: null,
};
expect(parsePendingThreadID('pending/dm_sidebar/12345')).toStrictEqual(
thickSidebarResult,
@@ -46,6 +49,7 @@
threadType: threadTypes.GENESIS_PERSONAL,
memberIDs: ['83810', '86622'],
sourceMessageID: null,
+ protocol: null,
};
expect(parsePendingThreadID('pending/type6/83810+86622')).toStrictEqual(
pendingPersonalResult,
@@ -55,6 +59,7 @@
threadType: threadTypes.COMMUNITY_OPEN_SUBTHREAD,
memberIDs: ['83810', '86622', '83889'],
sourceMessageID: null,
+ protocol: null,
};
expect(
parsePendingThreadID('pending/type3/83810+86622+83889'),
@@ -238,4 +243,37 @@
false,
);
});
+
+ it('should parse pending thread IDs with protocol correctly', () => {
+ // Test parsing ID with protocol
+ const result = parsePendingThreadID(
+ 'pending/type6/83810+86622/protocol_dc',
+ );
+ expect(result).toEqual({
+ threadType: threadTypes.GENESIS_PERSONAL,
+ memberIDs: ['83810', '86622'],
+ sourceMessageID: null,
+ protocol: 'dc',
+ });
+
+ // Test parsing ID without protocol
+ const resultNoProtocol = parsePendingThreadID('pending/type6/83810+86622');
+ expect(resultNoProtocol).toEqual({
+ threadType: threadTypes.GENESIS_PERSONAL,
+ memberIDs: ['83810', '86622'],
+ sourceMessageID: null,
+ protocol: null,
+ });
+
+ // Test sidebar with protocol
+ const sidebarResult = parsePendingThreadID(
+ 'pending/sidebar/12345/protocol_dm',
+ );
+ expect(sidebarResult).toEqual({
+ threadType: threadTypes.SIDEBAR,
+ memberIDs: [],
+ sourceMessageID: '12345',
+ protocol: 'dm',
+ });
+ });
});
diff --git a/lib/types/user-types.js b/lib/types/user-types.js
--- a/lib/types/user-types.js
+++ b/lib/types/user-types.js
@@ -11,6 +11,7 @@
type UserRelationshipStatus,
userRelationshipStatusValidator,
} from './relationship-types.js';
+import type { ProtocolName } from '../shared/threads/thread-spec.js';
import { tBool, tShape, tUserID } from '../utils/validation-utils.js';
export type GlobalUserInfo = {
@@ -124,4 +125,5 @@
+title: string,
},
+avatar?: ?ClientAvatar,
+ +supportedProtocols: Array<ProtocolName>,
};
diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js
--- a/lib/utils/validation-utils.js
+++ b/lib/utils/validation-utils.js
@@ -148,7 +148,7 @@
const pendingSidebarURLPrefix = 'sidebar';
const pendingThickSidebarURLPrefix = 'dm_sidebar';
-const pendingThreadIDRegex = `pending/(type[0-9]+/[0-9]+(\\+[0-9]+)*|(${pendingSidebarURLPrefix}|${pendingThickSidebarURLPrefix})/${idSchemaRegex})`;
+const pendingThreadIDRegex = `pending/(type[0-9]+/[0-9]+(\\+[0-9]+)*|(${pendingSidebarURLPrefix}|${pendingThickSidebarURLPrefix})/${idSchemaRegex})(/protocol_[^/]+)?`;
const chatNameMaxLength = 191;
const chatNameMinLength = 0;
diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js
--- a/native/chat/chat.react.js
+++ b/native/chat/chat.react.js
@@ -68,6 +68,7 @@
nuxTip,
NUXTipsContext,
} from '../components/nux-tips-context.react.js';
+import { ProtocolSelectionProvider } from '../components/protocol-selection-provider.react.js';
import { InputStateContext } from '../input/input-state.js';
import CommunityDrawerButton from '../navigation/community-drawer-button.react.js';
import HeaderBackButton from '../navigation/header-back-button.react.js';
@@ -458,68 +459,70 @@
const activeThreadID = activeThreadSelector(navContext);
return (
- <View style={styles.view}>
- <Chat.Navigator screenOptions={screenOptions}>
- <Chat.Screen
- name={ChatThreadListRouteName}
- component={ChatThreadsComponent}
- options={chatThreadListOptions}
- />
- <Chat.Screen
- name={MessageListRouteName}
- component={MessageListContainer}
- options={messageListOptions}
- />
- <Chat.Screen
- name={ComposeSubchannelRouteName}
- component={ComposeSubchannel}
- options={composeThreadOptions}
- />
- <Chat.Screen
- name={ThreadSettingsRouteName}
- component={ThreadSettings}
- options={threadSettingsOptions}
- />
- <Chat.Screen
- name={EmojiThreadAvatarCreationRouteName}
- component={EmojiThreadAvatarCreation}
- options={emojiAvatarCreationOptions}
- />
- <Chat.Screen
- name={FullScreenThreadMediaGalleryRouteName}
- component={FullScreenThreadMediaGallery}
- options={fullScreenThreadMediaGalleryOptions}
- />
- <Chat.Screen
- name={DeleteThreadRouteName}
- component={DeleteThread}
- options={deleteThreadOptions}
- />
- <Chat.Screen
- name={PinnedMessagesScreenRouteName}
- component={PinnedMessagesScreen}
- options={pinnedMessagesScreenOptions}
- />
- <Chat.Screen
- name={MessageSearchRouteName}
- component={MessageSearch}
- options={messageSearchOptions}
- />
- <Chat.Screen
- name={ChangeRolesScreenRouteName}
- component={ChangeRolesScreen}
- options={changeRolesScreenOptions}
- />
- <Chat.Screen
- name={ThreadSettingsNotificationsRouteName}
- component={ThreadSettingsNotifications}
- options={threadSettingsNotificationsOptions}
- />
- </Chat.Navigator>
- <MessageStorePruner frozen={frozen} activeThreadID={activeThreadID} />
- <ThreadScreenPruner />
- <NUXHandler />
- {draftUpdater}
- </View>
+ <ProtocolSelectionProvider>
+ <View style={styles.view}>
+ <Chat.Navigator screenOptions={screenOptions}>
+ <Chat.Screen
+ name={ChatThreadListRouteName}
+ component={ChatThreadsComponent}
+ options={chatThreadListOptions}
+ />
+ <Chat.Screen
+ name={MessageListRouteName}
+ component={MessageListContainer}
+ options={messageListOptions}
+ />
+ <Chat.Screen
+ name={ComposeSubchannelRouteName}
+ component={ComposeSubchannel}
+ options={composeThreadOptions}
+ />
+ <Chat.Screen
+ name={ThreadSettingsRouteName}
+ component={ThreadSettings}
+ options={threadSettingsOptions}
+ />
+ <Chat.Screen
+ name={EmojiThreadAvatarCreationRouteName}
+ component={EmojiThreadAvatarCreation}
+ options={emojiAvatarCreationOptions}
+ />
+ <Chat.Screen
+ name={FullScreenThreadMediaGalleryRouteName}
+ component={FullScreenThreadMediaGallery}
+ options={fullScreenThreadMediaGalleryOptions}
+ />
+ <Chat.Screen
+ name={DeleteThreadRouteName}
+ component={DeleteThread}
+ options={deleteThreadOptions}
+ />
+ <Chat.Screen
+ name={PinnedMessagesScreenRouteName}
+ component={PinnedMessagesScreen}
+ options={pinnedMessagesScreenOptions}
+ />
+ <Chat.Screen
+ name={MessageSearchRouteName}
+ component={MessageSearch}
+ options={messageSearchOptions}
+ />
+ <Chat.Screen
+ name={ChangeRolesScreenRouteName}
+ component={ChangeRolesScreen}
+ options={changeRolesScreenOptions}
+ />
+ <Chat.Screen
+ name={ThreadSettingsNotificationsRouteName}
+ component={ThreadSettingsNotifications}
+ options={threadSettingsNotificationsOptions}
+ />
+ </Chat.Navigator>
+ <MessageStorePruner frozen={frozen} activeThreadID={activeThreadID} />
+ <ThreadScreenPruner />
+ <NUXHandler />
+ {draftUpdater}
+ </View>
+ </ProtocolSelectionProvider>
);
}
diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js
--- a/native/chat/message-list-container.react.js
+++ b/native/chat/message-list-container.react.js
@@ -7,12 +7,8 @@
import { Text, View } from 'react-native';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import genesis from 'lib/facts/genesis.js';
-import {
- useUsersSupportFarcasterDCs,
- useUsersSupportingProtocols,
- useUsersSupportThickThreads,
-} from 'lib/hooks/user-identities-hooks.js';
import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js';
import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
@@ -270,6 +266,11 @@
const [userInfoInputArray, setUserInfoInputArray] = React.useState<
$ReadOnlyArray<AccountUserInfo>,
>([]);
+ const { setUserInfoInput, selectedProtocol, setSearching } =
+ useProtocolSelection();
+ React.useEffect(() => {
+ setUserInfoInput?.(userInfoInputArray);
+ }, [setUserInfoInput, userInfoInputArray]);
const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers);
const supportsFarcasterDCs = useIsFarcasterDCsIntegrationEnabled();
@@ -300,30 +301,26 @@
props.route.params.threadInfo,
);
- const existingThreadInfoFinder =
- useExistingThreadInfoFinder(baseThreadInfo);
-
- const checkUsersThickThreadSupport = useUsersSupportThickThreads();
- const checkUsersFarcasterDCsSupport = useUsersSupportFarcasterDCs();
- const { allUsersSupportThickThreads, allUsersSupportFarcasterThreads } =
- useUsersSupportingProtocols(userInfoInputArray);
+ const existingThreadInfoFinder = useExistingThreadInfoFinder(
+ baseThreadInfo,
+ selectedProtocol,
+ );
const isSearching = !!props.route.params.searching;
+ React.useEffect(() => {
+ setSearching?.(isSearching);
+ return () => {
+ setSearching?.(false);
+ };
+ }, [isSearching, setSearching]);
+
const threadInfo = React.useMemo(
() =>
existingThreadInfoFinder({
searching: isSearching,
userInfoInputArray,
- allUsersSupportThickThreads,
- allUsersSupportFarcasterThreads,
}),
- [
- allUsersSupportFarcasterThreads,
- allUsersSupportThickThreads,
- existingThreadInfoFinder,
- isSearching,
- userInfoInputArray,
- ],
+ [existingThreadInfoFinder, isSearching, userInfoInputArray],
);
invariant(
threadInfo,
@@ -386,24 +383,9 @@
const resolveToUser = React.useCallback(
async (user: AccountUserInfo) => {
const newUserInfoInputArray = user.id === viewerID ? [] : [user];
- const newUserIDs = newUserInfoInputArray.map(userInfo => userInfo.id);
- const [usersSupportingThickThreads, usersSupportingFarcasterThreads] =
- await Promise.all([
- checkUsersThickThreadSupport(newUserIDs),
- checkUsersFarcasterDCsSupport(newUserIDs),
- ]);
-
const resolvedThreadInfo = existingThreadInfoFinder({
searching: true,
userInfoInputArray: newUserInfoInputArray,
- allUsersSupportThickThreads:
- user.id === viewerID
- ? true
- : !!usersSupportingThickThreads.get(user.id),
- allUsersSupportFarcasterThreads:
- user.id === viewerID
- ? false
- : !!usersSupportingFarcasterThreads.get(user.id),
});
invariant(
resolvedThreadInfo,
@@ -413,14 +395,7 @@
setBaseThreadInfo(resolvedThreadInfo);
setParams({ searching: false, threadInfo: resolvedThreadInfo });
},
- [
- viewerID,
- checkUsersThickThreadSupport,
- checkUsersFarcasterDCsSupport,
- existingThreadInfoFinder,
- editInputMessage,
- setParams,
- ],
+ [viewerID, existingThreadInfoFinder, editInputMessage, setParams],
);
const messageListData = useNativeMessageListData({
diff --git a/native/components/protocol-selection-provider.react.js b/native/components/protocol-selection-provider.react.js
new file mode 100644
--- /dev/null
+++ b/native/components/protocol-selection-provider.react.js
@@ -0,0 +1,106 @@
+// @flow
+
+import * as React from 'react';
+
+import { ProtocolSelectionContext } from 'lib/contexts/protocol-selection-context.js';
+import { useUsersSupportingProtocols } from 'lib/hooks/user-identities-hooks.js';
+import { protocolNames } from 'lib/shared/protocol-names.js';
+import { threadIsPending } from 'lib/shared/thread-utils.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
+import type { AccountUserInfo } from 'lib/types/user-types.js';
+import { useCurrentUserSupportsDCs } from 'lib/utils/farcaster-utils.js';
+
+import { useActiveThread } from '../navigation/nav-selectors.js';
+
+type ProtocolSelectionProviderProps = {
+ +children: React.Node,
+};
+
+function ProtocolSelectionProvider(
+ props: ProtocolSelectionProviderProps,
+): React.Node {
+ const { children } = props;
+
+ const [selectedProtocol, setSelectedProtocol] =
+ React.useState<?ProtocolName>(null);
+ const [isSearching, setSearching] = React.useState(false);
+ const [userInfoInputArray, setUserInfoInputArray] = React.useState<
+ $ReadOnlyArray<AccountUserInfo>,
+ >([]);
+
+ const activeThread = useActiveThread();
+ const isThreadPending = React.useMemo(
+ () => activeThread && threadIsPending(activeThread),
+ [activeThread],
+ );
+
+ React.useEffect(() => {
+ if (!isSearching) {
+ setSelectedProtocol(null);
+ setUserInfoInputArray([]);
+ }
+ }, [isSearching, selectedProtocol]);
+
+ const { allUsersSupportThickThreads, allUsersSupportFarcasterThreads } =
+ useUsersSupportingProtocols(userInfoInputArray);
+ const currentUserSupportsDCs = useCurrentUserSupportsDCs();
+
+ const availableProtocols = React.useMemo(() => {
+ const protocols: Array<ProtocolName> = [];
+ if (
+ (userInfoInputArray.length === 0 || allUsersSupportFarcasterThreads) &&
+ currentUserSupportsDCs
+ ) {
+ protocols.push(protocolNames.FARCASTER_DC);
+ }
+ if (userInfoInputArray.length === 0 || allUsersSupportThickThreads) {
+ protocols.push(protocolNames.COMM_DM);
+ }
+ return protocols.filter(protocol => protocol !== selectedProtocol);
+ }, [
+ allUsersSupportFarcasterThreads,
+ allUsersSupportThickThreads,
+ currentUserSupportsDCs,
+ selectedProtocol,
+ userInfoInputArray.length,
+ ]);
+
+ React.useEffect(() => {
+ if (userInfoInputArray.length === 0 || !isThreadPending) {
+ return;
+ }
+ if (allUsersSupportFarcasterThreads && !allUsersSupportThickThreads) {
+ setSelectedProtocol(protocolNames.FARCASTER_DC);
+ } else if (
+ !allUsersSupportFarcasterThreads &&
+ allUsersSupportThickThreads
+ ) {
+ setSelectedProtocol(protocolNames.COMM_DM);
+ }
+ }, [
+ allUsersSupportFarcasterThreads,
+ allUsersSupportThickThreads,
+ isThreadPending,
+ selectedProtocol,
+ userInfoInputArray.length,
+ ]);
+
+ const contextValue = React.useMemo(
+ () => ({
+ selectedProtocol,
+ setSelectedProtocol,
+ availableProtocols,
+ setUserInfoInput: setUserInfoInputArray,
+ setSearching,
+ }),
+ [availableProtocols, selectedProtocol],
+ );
+
+ return (
+ <ProtocolSelectionContext.Provider value={contextValue}>
+ {children}
+ </ProtocolSelectionContext.Provider>
+ );
+}
+
+export { ProtocolSelectionProvider };
diff --git a/native/components/user-list-user.react.js b/native/components/user-list-user.react.js
--- a/native/components/user-list-user.react.js
+++ b/native/components/user-list-user.react.js
@@ -78,7 +78,13 @@
onSelect = () => {
const { userInfo } = this.props;
if (!userInfo.alert) {
- const { alert, notice, disabled, ...accountUserInfo } = userInfo;
+ const {
+ alert,
+ notice,
+ disabled,
+ supportedProtocols,
+ ...accountUserInfo
+ } = userInfo;
this.props.onSelect(accountUserInfo);
return;
}
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -70,6 +70,7 @@
import LogOutIfMissingCSATHandler from './components/log-out-if-missing-csat-handler.react.js';
import NavigationArrows from './components/navigation-arrows.react.js';
import NonKeyserverActivityHandler from './components/non-keyserver-activity-handler.react.js';
+import { ProtocolSelectionProvider } from './components/protocol-selection-provider.react.js';
import MinVersionHandler from './components/version-handler.react.js';
import { olmAPI } from './crypto/olm-api.js';
import { sqliteAPI } from './database/sqlite-api.js';
@@ -400,10 +401,12 @@
css['main-content-container-column'],
);
return (
- <div className={mainContentClass}>
- <Topbar />
- <div className={css['main-content']}>{mainContent}</div>
- </div>
+ <ProtocolSelectionProvider>
+ <div className={mainContentClass}>
+ <Topbar />
+ <div className={css['main-content']}>{mainContent}</div>
+ </div>
+ </ProtocolSelectionProvider>
);
}
}
diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js
--- a/web/chat/chat-message-list-container.react.js
+++ b/web/chat/chat-message-list-container.react.js
@@ -6,6 +6,7 @@
import { useDrop } from 'react-dnd';
import { NativeTypes } from 'react-dnd-html5-backend';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
import { threadSpecs } from 'lib/shared/threads/thread-specs.js';
@@ -32,7 +33,11 @@
const { activeChatThreadID } = props;
const { isChatCreation, selectedUserInfos } = useInfosForPendingThread();
- const threadInfo = useThreadInfoForPossiblyPendingThread(activeChatThreadID);
+ const { selectedProtocol } = useProtocolSelection();
+ const threadInfo = useThreadInfoForPossiblyPendingThread(
+ activeChatThreadID,
+ selectedProtocol,
+ );
invariant(threadInfo, 'ThreadInfo should be set');
const dispatch = useDispatch();
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
--- a/web/chat/chat-thread-composer.react.js
+++ b/web/chat/chat-thread-composer.react.js
@@ -7,12 +7,9 @@
import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/swmansion-icon.react.js';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
import { useResolvableNames } from 'lib/hooks/names-cache.js';
-import {
- useUsersSupportFarcasterDCs,
- useUsersSupportThickThreads,
-} from 'lib/hooks/user-identities-hooks.js';
import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js';
import { extractFIDFromUserID } from 'lib/shared/id-utils.js';
import {
@@ -25,7 +22,8 @@
threadIsPending,
useExistingThreadInfoFinder,
} from 'lib/shared/thread-utils.js';
-import { threadTypes } from 'lib/types/thread-types-enum.js';
+import { dmThreadProtocol } from 'lib/shared/threads/protocols/dm-thread-protocol.js';
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { useIsFarcasterDCsIntegrationEnabled } from 'lib/utils/services-utils.js';
@@ -55,6 +53,7 @@
const [usernameInputText, setUsernameInputText] = React.useState('');
+ const { selectedProtocol } = useProtocolSelection();
const dispatch = useDispatch();
const loggedInUserInfo = useLoggedInUserInfo();
@@ -91,20 +90,21 @@
const { pushModal } = useModalContext();
- const pendingPrivateThread = React.useRef(
- createPendingThread({
+ const pendingThread = React.useMemo(() => {
+ const protocol = getProtocolByName(selectedProtocol) ?? dmThreadProtocol;
+
+ return createPendingThread({
viewerID,
- threadType: threadTypes.PRIVATE,
+ threadType: protocol.pendingThreadType(userInfoInputArray.length),
members: [loggedInUserInfo],
- }),
- );
+ });
+ }, [loggedInUserInfo, selectedProtocol, userInfoInputArray.length, viewerID]);
+
const existingThreadInfoFinderForCreatingThread = useExistingThreadInfoFinder(
- pendingPrivateThread.current,
+ pendingThread,
+ selectedProtocol,
);
- const checkUsersThickThreadSupport = useUsersSupportThickThreads();
- const checkUsersFarcasterDCsSupport = useUsersSupportFarcasterDCs();
-
const onSelectUserFromSearch = React.useCallback(
async (userListItem: UserListItem) => {
const { alert, notice, disabled, ...user } = userListItem;
@@ -121,25 +121,10 @@
username: userListItem.username,
};
const newUserInfoInputArray = user.id === viewerID ? [] : [newUserInfo];
- const newUserIDs = newUserInfoInputArray.map(userInfo => userInfo.id);
-
- const [usersSupportingThickThreads, usersSupportingFarcasterThreads] =
- await Promise.all([
- checkUsersThickThreadSupport(newUserIDs),
- checkUsersFarcasterDCsSupport(newUserIDs),
- ]);
const threadInfo = existingThreadInfoFinderForCreatingThread({
searching: true,
userInfoInputArray: newUserInfoInputArray,
- allUsersSupportThickThreads:
- user.id === viewerID
- ? true
- : !!usersSupportingThickThreads.get(user.id),
- allUsersSupportFarcasterThreads:
- user.id === viewerID
- ? false
- : !!usersSupportingFarcasterThreads.get(user.id),
});
dispatch({
type: updateNavInfoActionType,
@@ -163,8 +148,6 @@
[
viewerID,
userInfoInputArray,
- checkUsersThickThreadSupport,
- checkUsersFarcasterDCsSupport,
existingThreadInfoFinderForCreatingThread,
dispatch,
pushModal,
diff --git a/web/components/protocol-selection-provider.react.js b/web/components/protocol-selection-provider.react.js
new file mode 100644
--- /dev/null
+++ b/web/components/protocol-selection-provider.react.js
@@ -0,0 +1,111 @@
+// @flow
+
+import * as React from 'react';
+
+import { ProtocolSelectionContext } from 'lib/contexts/protocol-selection-context.js';
+import { useUsersSupportingProtocols } from 'lib/hooks/user-identities-hooks.js';
+import { protocolNames } from 'lib/shared/protocol-names.js';
+import { threadIsPending } from 'lib/shared/thread-utils.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
+import { useCurrentUserSupportsDCs } from 'lib/utils/farcaster-utils.js';
+
+import { useSelector } from '../redux/redux-utils.js';
+import {
+ useInfosForPendingThread,
+ useThreadInfoForPossiblyPendingThread,
+} from '../utils/thread-utils.js';
+
+type ProtocolSelectionProviderProps = {
+ +children: React.Node,
+};
+
+function ProtocolSelectionProvider(
+ props: ProtocolSelectionProviderProps,
+): React.Node {
+ const { children } = props;
+
+ const [selectedProtocol, setSelectedProtocol] =
+ React.useState<?ProtocolName>(null);
+
+ const activeChatThreadID = useSelector(
+ state => state.navInfo.activeChatThreadID,
+ );
+
+ const threadInfo = useThreadInfoForPossiblyPendingThread(
+ activeChatThreadID,
+ selectedProtocol,
+ );
+ const { isChatCreation, selectedUserInfos } = useInfosForPendingThread();
+
+ const isThreadPending = React.useMemo(
+ () => threadInfo && threadIsPending(threadInfo.id),
+ [threadInfo],
+ );
+
+ React.useEffect(() => {
+ if (!isChatCreation) {
+ setSelectedProtocol(null);
+ }
+ }, [isChatCreation]);
+
+ const { allUsersSupportThickThreads, allUsersSupportFarcasterThreads } =
+ useUsersSupportingProtocols(selectedUserInfos);
+ const currentUserSupportsDCs = useCurrentUserSupportsDCs();
+
+ const availableProtocols = React.useMemo(() => {
+ const protocols: Array<ProtocolName> = [];
+ if (
+ (selectedUserInfos.length === 0 || allUsersSupportFarcasterThreads) &&
+ currentUserSupportsDCs
+ ) {
+ protocols.push(protocolNames.FARCASTER_DC);
+ }
+ if (selectedUserInfos.length === 0 || allUsersSupportThickThreads) {
+ protocols.push(protocolNames.COMM_DM);
+ }
+ return protocols.filter(protocol => protocol !== selectedProtocol);
+ }, [
+ allUsersSupportFarcasterThreads,
+ allUsersSupportThickThreads,
+ currentUserSupportsDCs,
+ selectedProtocol,
+ selectedUserInfos.length,
+ ]);
+
+ React.useEffect(() => {
+ if (selectedUserInfos.length === 0 || !isThreadPending) {
+ return;
+ }
+ if (allUsersSupportFarcasterThreads && !allUsersSupportThickThreads) {
+ setSelectedProtocol(protocolNames.FARCASTER_DC);
+ } else if (
+ !allUsersSupportFarcasterThreads &&
+ allUsersSupportThickThreads
+ ) {
+ setSelectedProtocol(protocolNames.COMM_DM);
+ }
+ }, [
+ allUsersSupportFarcasterThreads,
+ allUsersSupportThickThreads,
+ isThreadPending,
+ selectedProtocol,
+ selectedUserInfos.length,
+ ]);
+
+ const contextValue = React.useMemo(
+ () => ({
+ selectedProtocol,
+ setSelectedProtocol,
+ availableProtocols,
+ }),
+ [availableProtocols, selectedProtocol],
+ );
+
+ return (
+ <ProtocolSelectionContext.Provider value={contextValue}>
+ {children}
+ </ProtocolSelectionContext.Provider>
+ );
+}
+
+export { ProtocolSelectionProvider };
diff --git a/web/navigation-panels/topbar.react.js b/web/navigation-panels/topbar.react.js
--- a/web/navigation-panels/topbar.react.js
+++ b/web/navigation-panels/topbar.react.js
@@ -4,6 +4,7 @@
import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/swmansion-icon.react.js';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import AppSwitcher from './app-switcher.react.js';
import NavStateInfoBar from './nav-state-info-bar.react.js';
@@ -31,7 +32,11 @@
);
const activeChatThreadID = useDrawerSelectedThreadID();
- const threadInfo = useThreadInfoForPossiblyPendingThread(activeChatThreadID);
+ const { selectedProtocol } = useProtocolSelection();
+ const threadInfo = useThreadInfoForPossiblyPendingThread(
+ activeChatThreadID,
+ selectedProtocol,
+ );
return (
<>
diff --git a/web/utils/thread-utils.js b/web/utils/thread-utils.js
--- a/web/utils/thread-utils.js
+++ b/web/utils/thread-utils.js
@@ -4,14 +4,15 @@
import * as React from 'react';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
-import { useUsersSupportingProtocols } from 'lib/hooks/user-identities-hooks.js';
import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
import {
createPendingThread,
useExistingThreadInfoFinder,
} from 'lib/shared/thread-utils.js';
+import { dmThreadProtocol } from 'lib/shared/threads/protocols/dm-thread-protocol.js';
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
-import { threadTypes } from 'lib/types/thread-types-enum.js';
import type { AccountUserInfo } from 'lib/types/user-types.js';
import { useSelector } from '../redux/redux-utils.js';
@@ -36,35 +37,39 @@
function useThreadInfoForPossiblyPendingThread(
activeChatThreadID: ?string,
+ selectedProtocol: ?ProtocolName,
): ?ThreadInfo {
const { isChatCreation, selectedUserInfos } = useInfosForPendingThread();
const loggedInUserInfo = useLoggedInUserInfo();
invariant(loggedInUserInfo, 'loggedInUserInfo should be set');
- const pendingPrivateThread = React.useRef(
- createPendingThread({
+ const pendingThread = React.useMemo(() => {
+ const protocol = getProtocolByName(selectedProtocol) ?? dmThreadProtocol;
+ return createPendingThread({
viewerID: loggedInUserInfo.id,
- threadType: threadTypes.PRIVATE,
+ threadType: protocol.pendingThreadType(1),
members: [loggedInUserInfo],
- }),
- );
+ });
+ }, [loggedInUserInfo, selectedProtocol]);
const newThreadID = 'pending/new_thread';
- const pendingNewThread = React.useMemo(
- () => ({
+ const pendingNewThread = React.useMemo(() => {
+ const protocol = getProtocolByName(selectedProtocol) ?? dmThreadProtocol;
+ return {
...createPendingThread({
viewerID: loggedInUserInfo.id,
- threadType: threadTypes.PRIVATE,
+ threadType: protocol.pendingThreadType(1),
members: [loggedInUserInfo],
name: 'New thread',
}),
id: newThreadID,
- }),
- [loggedInUserInfo],
- );
+ };
+ }, [loggedInUserInfo, selectedProtocol]);
+
const existingThreadInfoFinderForCreatingThread = useExistingThreadInfoFinder(
- pendingPrivateThread.current,
+ pendingThread,
+ selectedProtocol,
);
const baseThreadInfo = useSelector(state => {
@@ -76,10 +81,10 @@
}
return state.navInfo.pendingThread;
});
- const existingThreadInfoFinder = useExistingThreadInfoFinder(baseThreadInfo);
-
- const { allUsersSupportThickThreads, allUsersSupportFarcasterThreads } =
- useUsersSupportingProtocols(selectedUserInfos);
+ const existingThreadInfoFinder = useExistingThreadInfoFinder(
+ baseThreadInfo,
+ selectedProtocol,
+ );
const threadInfo = React.useMemo(() => {
if (isChatCreation) {
@@ -90,20 +95,14 @@
return existingThreadInfoFinderForCreatingThread({
searching: true,
userInfoInputArray: selectedUserInfos,
- allUsersSupportThickThreads,
- allUsersSupportFarcasterThreads,
});
}
return existingThreadInfoFinder({
searching: false,
userInfoInputArray: [],
- allUsersSupportThickThreads: true,
- allUsersSupportFarcasterThreads: true,
});
}, [
- allUsersSupportFarcasterThreads,
- allUsersSupportThickThreads,
existingThreadInfoFinder,
existingThreadInfoFinderForCreatingThread,
isChatCreation,

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 8:03 PM (8 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840033
Default Alt Text
D15346.1765051422.diff (45 KB)

Event Timeline