Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32162154
D15346.1765044686.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
45 KB
Referenced Files
None
Subscribers
None
D15346.1765044686.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 6:11 PM (7 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840033
Default Alt Text
D15346.1765044686.diff (45 KB)
Attached To
Mode
D15346: [lib][web][native] implement logic for selecting thread protocol
Attached
Detach File
Event Timeline
Log In to Comment