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 @@ -33,6 +33,7 @@ roleIsAdminRole, threadIsPending, getPendingThreadID, + pendingThreadType, } from '../shared/thread-utils.js'; import type { ClientAvatar, ClientEmojiAvatar } from '../types/avatar-types'; import type { EntryInfo } from '../types/entry-types.js'; @@ -461,17 +462,28 @@ const actualMemberIDs = rawThreadInfo.members .filter(member => member.role) .map(member => member.id); - const pendingThreadID = getPendingThreadID( - rawThreadInfo.type, - actualMemberIDs, - rawThreadInfo.sourceMessageID, - ); - const existingResult = result.get(pendingThreadID); - if ( - !existingResult || - rawThreadInfos[existingResult].creationTime > rawThreadInfo.creationTime - ) { - result.set(pendingThreadID, threadID); + // In this function we're generating possible pending thread IDs that + // could become `rawThreadInfos`. It is possible that a thick pending + // thread becomes a thin thread, so we're including it twice in a map - + // for each possible pending thread ID. + const possiblePendingThreadTypes = [ + pendingThreadType(actualMemberIDs.length - 1, 'thin', true), + pendingThreadType(actualMemberIDs.length - 1, 'thick', true), + ]; + 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); + } } } return result; diff --git a/lib/shared/thread-actions-utils.js b/lib/shared/thread-actions-utils.js --- a/lib/shared/thread-actions-utils.js +++ b/lib/shared/thread-actions-utils.js @@ -12,6 +12,7 @@ removeUsersFromThreadActionTypes, type RemoveUsersFromThreadInput, } from '../actions/thread-actions.js'; +import type { AuxUserInfos } from '../types/aux-user-types.js'; import type { CalendarQuery } from '../types/entry-types.js'; import type { RelativeMemberInfo, @@ -23,6 +24,7 @@ assertThickThreadType, threadTypeIsThick, } from '../types/thread-types-enum.js'; +import type { ThreadType } from '../types/thread-types-enum.js'; import type { ChangeThreadSettingsPayload, ClientNewThinThreadRequest, @@ -60,6 +62,7 @@ +handleError?: () => mixed, +calendarQuery: CalendarQuery, +usingOlmViaTunnelbrokerForDMs: boolean, + +auxUserInfos: AuxUserInfos, }; async function createRealThreadFromPendingThread({ @@ -71,12 +74,20 @@ viewerID, calendarQuery, usingOlmViaTunnelbrokerForDMs, -}: CreateRealThreadParameters): Promise { + auxUserInfos, +}: CreateRealThreadParameters): Promise<{ + +threadID: string, + +threadType: ThreadType, +}> { if (!threadIsPending(threadInfo.id)) { - return threadInfo.id; + return { + threadID: threadInfo.id, + threadType: threadInfo.type, + }; } let newThreadID; + let newThreadType = threadInfo.type; const otherMemberIDs = threadOtherMembers(threadInfo.members, viewerID).map( member => member.id, @@ -125,7 +136,12 @@ otherMemberIDs.length > 0, 'otherMemberIDs should not be empty for threads', ); - if (threadTypeIsThick(threadInfo.type)) { + const allUsersSupportThickThreads = otherMemberIDs.every( + memberID => + auxUserInfos[memberID]?.deviceList && + auxUserInfos[memberID].deviceList.devices.length > 0, + ); + if (threadTypeIsThick(threadInfo.type) && allUsersSupportThickThreads) { const type = assertThickThreadType( pendingThreadType( otherMemberIDs.length, @@ -144,6 +160,7 @@ initialMemberIDs: otherMemberIDs, color: threadInfo.color, }); + newThreadType = type; } else { const type = assertThinThreadType( pendingThreadType( @@ -166,9 +183,13 @@ void dispatchActionPromise(newThreadActionTypes, resultPromise); const result = await resultPromise; newThreadID = result.newThreadID; + newThreadType = type; } } - return newThreadID; + return { + threadID: newThreadID, + threadType: newThreadType, + }; } export { removeMemberFromThread, createRealThreadFromPendingThread }; diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js --- a/native/input/input-state-container.react.js +++ b/native/input/input-state-container.react.js @@ -57,6 +57,7 @@ threadIsPending, threadIsPendingSidebar, } from 'lib/shared/thread-utils.js'; +import type { AuxUserInfos } from 'lib/types/aux-user-types'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import type { Media, @@ -85,6 +86,7 @@ threadTypeIsThick, threadTypeIsSidebar, } from 'lib/types/thread-types-enum.js'; +import type { ThreadType } from 'lib/types/thread-types-enum.js'; import { type ClientNewThinThreadRequest, type NewThreadResult, @@ -170,6 +172,7 @@ +newThickThread: (request: NewThickThreadRequest) => Promise, +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, +usingOlmViaTunnelbrokerForDMs: boolean, + +auxUserInfos: AuxUserInfos, }; type State = { +pendingUploads: PendingMultimediaUploads, @@ -185,7 +188,13 @@ (params: EditInputBarMessageParameters) => void, > = []; scrollToMessageCallbacks: Array<(messageID: string) => void> = []; - pendingThreadCreations: Map> = new Map(); + pendingThreadCreations: Map< + string, + Promise<{ + +threadID: string, + +threadType: ThreadType, + }>, + > = new Map(); pendingThreadUpdateHandlers: Map mixed> = new Map(); // TODO: flip the switch // Note that this enables Blob service for encrypted media only @@ -349,7 +358,8 @@ // again. throw new Error('Thread creation failed'); } - newThreadID = await threadCreationPromise; + const result = await threadCreationPromise; + newThreadID = result.threadID; } catch (e) { const copy = cloneError(e); copy.localID = messageInfo.localID; @@ -505,9 +515,9 @@ } } - let newThreadID = null; + let threadCreationResult = null; try { - newThreadID = await this.startThreadCreation(threadInfo); + threadCreationResult = await this.startThreadCreation(threadInfo); } catch (e) { const copy = cloneError(e); copy.localID = messageInfo.localID; @@ -524,13 +534,14 @@ const newMessageInfo = { ...messageInfo, - threadID: newThreadID, + threadID: threadCreationResult?.threadID, time: Date.now(), }; const newThreadInfo = { ...threadInfo, - id: newThreadID, + id: threadCreationResult?.threadID, + type: threadCreationResult?.threadType ?? threadInfo.type, }; void this.props.dispatchActionPromise( @@ -545,9 +556,14 @@ ); }; - startThreadCreation(threadInfo: ThreadInfo): Promise { + startThreadCreation( + threadInfo: ThreadInfo, + ): Promise<{ +threadID: string, +threadType: ThreadType }> { if (!threadIsPending(threadInfo.id)) { - return Promise.resolve(threadInfo.id); + return Promise.resolve({ + threadID: threadInfo.id, + threadType: threadInfo.type, + }); } let threadCreationPromise = this.pendingThreadCreations.get(threadInfo.id); if (!threadCreationPromise) { @@ -561,6 +577,7 @@ viewerID: this.props.viewerID, calendarQuery, usingOlmViaTunnelbrokerForDMs: this.props.usingOlmViaTunnelbrokerForDMs, + auxUserInfos: this.props.auxUserInfos, }); this.pendingThreadCreations.set(threadInfo.id, threadCreationPromise); } @@ -1773,6 +1790,7 @@ const textMessageCreationSideEffectsFunc = useMessageCreationSideEffectsFunc(messageTypes.TEXT); const usingOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); return ( ); }); diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js --- a/web/input/input-state-container.react.js +++ b/web/input/input-state-container.react.js @@ -59,6 +59,7 @@ threadIsPending, threadIsPendingSidebar, } from 'lib/shared/thread-utils.js'; +import type { AuxUserInfos } from 'lib/types/aux-user-types.js'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import type { MediaMission, @@ -83,6 +84,7 @@ threadTypeIsSidebar, threadTypeIsThick, } from 'lib/types/thread-types-enum.js'; +import type { ThreadType } from 'lib/types/thread-types-enum.js'; import { type ClientNewThinThreadRequest, type NewThreadResult, @@ -170,6 +172,7 @@ +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, +identityContext: ?IdentityClientContextType, +usingOlmViaTunnelbrokerForDMs: boolean, + +auxUserInfos: AuxUserInfos, }; type WritableState = { pendingUploads: { @@ -201,9 +204,18 @@ }, }; replyCallbacks: Array<(message: string) => void> = []; - pendingThreadCreations: Map> = new Map< + pendingThreadCreations: Map< string, - Promise, + Promise<{ + +threadID: string, + +threadType: ThreadType, + }>, + > = new Map< + string, + Promise<{ + +threadID: string, + +threadType: ThreadType, + }>, >(); // TODO: flip the switch // Note that this enables Blob service for encrypted media only @@ -481,7 +493,8 @@ // again. throw new Error('Thread creation failed'); } - newThreadID = await threadCreationPromise; + const result = await threadCreationPromise; + newThreadID = result.threadID; } catch (e) { const copy = cloneError(e); copy.localID = messageInfo.localID; @@ -577,9 +590,15 @@ } } - startThreadCreation(threadInfo: ThreadInfo): Promise { + startThreadCreation(threadInfo: ThreadInfo): Promise<{ + +threadID: string, + +threadType: ThreadType, + }> { if (!threadIsPending(threadInfo.id)) { - return Promise.resolve(threadInfo.id); + return Promise.resolve({ + threadID: threadInfo.id, + threadType: threadInfo.type, + }); } let threadCreationPromise = this.pendingThreadCreations.get(threadInfo.id); if (!threadCreationPromise) { @@ -593,6 +612,7 @@ viewerID: this.props.viewerID, calendarQuery, usingOlmViaTunnelbrokerForDMs: this.props.usingOlmViaTunnelbrokerForDMs, + auxUserInfos: this.props.auxUserInfos, }); this.pendingThreadCreations.set(threadInfo.id, threadCreationPromise); } @@ -1323,9 +1343,9 @@ } } - let newThreadID = null; + let threadCreationResult = null; try { - newThreadID = await this.startThreadCreation(threadInfo); + threadCreationResult = await this.startThreadCreation(threadInfo); } catch (e) { const copy = cloneError(e); copy.localID = messageInfo.localID; @@ -1342,13 +1362,14 @@ const newMessageInfo = { ...messageInfo, - threadID: newThreadID, + threadID: threadCreationResult?.threadID, time: Date.now(), }; const newThreadInfo = { ...threadInfo, - id: newThreadID, + id: threadCreationResult?.threadID, + type: threadCreationResult?.threadType ?? threadInfo.type, }; void this.props.dispatchActionPromise( sendTextMessageActionTypes, @@ -1709,6 +1730,7 @@ const textMessageCreationSideEffectsFunc = useMessageCreationSideEffectsFunc(messageTypes.TEXT); const usingOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); return ( ); });