diff --git a/lib/actions/thread-actions.js b/lib/actions/thread-actions.js --- a/lib/actions/thread-actions.js +++ b/lib/actions/thread-actions.js @@ -11,14 +11,19 @@ import { type OutboundDMOperationSpecification, dmOperationSpecificationTypes, + getCreateThickRawThreadInfoInputFromThreadInfo, } from '../shared/dm-ops/dm-op-utils.js'; import { useProcessAndSendDMOperation } from '../shared/dm-ops/process-dm-ops.js'; import { permissionsAndAuthRelatedRequestTimeout } from '../shared/timeouts.js'; import type { DMChangeThreadSettingsOperation, DMThreadSettingsChanges, + DMJoinThreadOperation, } from '../types/dm-ops.js'; -import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; +import type { + ThreadInfo, + RawThreadInfo, +} from '../types/minimally-encoded-thread-permissions-types.js'; import { thickThreadTypes, threadTypeIsThick, @@ -346,10 +351,69 @@ }; }; +export type UseJoinThreadInput = + | { + +thick: false, + +request: ClientThreadJoinRequest, + } + | { + +thick: true, + +rawThreadInfo: RawThreadInfo, + }; + function useJoinThread(): ( - input: ClientThreadJoinRequest, + input: UseJoinThreadInput, ) => Promise { - return useKeyserverCall(joinThread); + const processAndSendDMOperation = useProcessAndSendDMOperation(); + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + const keyserverCall = useKeyserverCall(joinThread); + return React.useCallback( + async (input: UseJoinThreadInput) => { + if (!input.thick) { + return await keyserverCall(input.request); + } + + const { rawThreadInfo } = input; + + invariant(viewerID, 'viewerID should be set'); + invariant(rawThreadInfo.thick, 'thread must be thick'); + + const existingThreadDetails = + getCreateThickRawThreadInfoInputFromThreadInfo(rawThreadInfo); + + const op: DMJoinThreadOperation = { + type: 'join_thread', + joinerID: viewerID, + time: Date.now(), + messageID: uuid.v4(), + existingThreadDetails, + }; + + const opSpecification: OutboundDMOperationSpecification = { + type: dmOperationSpecificationTypes.OUTBOUND, + op, + recipients: { + type: 'all_thread_members', + threadID: + rawThreadInfo.type === thickThreadTypes.THICK_SIDEBAR && + rawThreadInfo.parentThreadID + ? rawThreadInfo.parentThreadID + : rawThreadInfo.id, + }, + }; + + await processAndSendDMOperation(opSpecification); + return ({ + updatesResult: { newUpdates: [] }, + rawMessageInfos: [], + truncationStatuses: {}, + userInfos: [], + }: ThreadJoinPayload); + }, + [keyserverCall, processAndSendDMOperation, viewerID], + ); } export type LeaveThreadInput = { diff --git a/lib/shared/community-utils.js b/lib/shared/community-utils.js --- a/lib/shared/community-utils.js +++ b/lib/shared/community-utils.js @@ -210,6 +210,10 @@ const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); + const rawThreadInfo = useSelector(state => + threadID ? state.threadStore.threadInfos[threadID] : null, + ); + const callJoinThread = useJoinThread(); let keyserverID = keyserverOverride?.keyserverID; @@ -339,17 +343,20 @@ const communityThreadID = ongoingJoinData.communityID; const query = calendarQuery(); const joinThreadPromise = callJoinThread({ - threadID: communityThreadID, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [communityThreadID] }, - ], + thick: false, + request: { + threadID: communityThreadID, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [communityThreadID] }, + ], + }, + inviteLinkSecret: inviteSecret, + defaultSubscription, }, - inviteLinkSecret: inviteSecret, - defaultSubscription, }); threadJoinPromiseRef.current = joinThreadPromise; void dispatchActionPromise(joinThreadActionTypes, joinThreadPromise); @@ -397,18 +404,30 @@ } const query = calendarQuery(); - const joinThreadPromise = callJoinThread({ - threadID, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [threadID] }, - ], - }, - inviteLinkSecret: inviteSecret, - }); + let joinThreadInput; + if (rawThreadInfo && rawThreadInfo.thick) { + joinThreadInput = { + thick: true, + rawThreadInfo, + }; + } else { + joinThreadInput = { + thick: false, + request: { + threadID, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [threadID] }, + ], + }, + }, + }; + } + + const joinThreadPromise = callJoinThread(joinThreadInput); void dispatchActionPromise(joinThreadActionTypes, joinThreadPromise); try { @@ -429,6 +448,7 @@ } })(); }, [ + rawThreadInfo, calendarQuery, callJoinThread, dispatchActionPromise, diff --git a/lib/shared/dm-ops/dm-op-utils.js b/lib/shared/dm-ops/dm-op-utils.js --- a/lib/shared/dm-ops/dm-op-utils.js +++ b/lib/shared/dm-ops/dm-op-utils.js @@ -239,4 +239,8 @@ ); } -export { createMessagesToPeersFromDMOp, useAddDMThreadMembers }; +export { + createMessagesToPeersFromDMOp, + useAddDMThreadMembers, + getCreateThickRawThreadInfoInputFromThreadInfo, +}; diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -401,7 +401,7 @@ +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: $ReadOnlyArray, - +keyserverID: string, + +keyserverID?: string, }; export type ThreadFetchMediaResult = { diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -31,6 +31,7 @@ newThreadActionTypes, useJoinThread, } from 'lib/actions/thread-actions.js'; +import type { UseJoinThreadInput } from 'lib/actions/thread-actions.js'; import { useChatMentionContext, useThreadChatMentionCandidates, @@ -73,12 +74,12 @@ import type { RelativeMemberInfo, ThreadInfo, + RawThreadInfo, } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { Dispatch } from 'lib/types/redux-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import type { ChatMentionCandidates, - ClientThreadJoinRequest, ThreadJoinPayload, } from 'lib/types/thread-types.js'; import { @@ -276,6 +277,7 @@ type Props = { ...BaseProps, +viewerID: ?string, + +rawThreadInfo: RawThreadInfo, +draft: string, +joinThreadLoadingStatus: LoadingStatus, +threadCreationInProgress: boolean, @@ -288,7 +290,7 @@ +keyboardState: ?KeyboardState, +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, - +joinThread: (request: ClientThreadJoinRequest) => Promise, + +joinThread: (input: UseJoinThreadInput) => Promise, +inputState: ?InputState, +userMentionsCandidates: $ReadOnlyArray, +chatMentionSearchIndex: ?SentencePrefixSearchIndex, @@ -1143,18 +1145,31 @@ }; async joinAction(): Promise { - const query = this.props.calendarQuery(); - return await this.props.joinThread({ - threadID: this.props.threadInfo.id, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [this.props.threadInfo.id] }, - ], - }, - }); + let joinThreadInput; + if (this.props.rawThreadInfo.thick) { + joinThreadInput = { + thick: true, + rawThreadInfo: this.props.rawThreadInfo, + }; + } else { + const query = this.props.calendarQuery(); + joinThreadInput = { + thick: false, + request: { + threadID: this.props.threadInfo.id, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [this.props.threadInfo.id] }, + ], + }, + }, + }; + } + + return await this.props.joinThread(joinThreadInput); } expandButtons = () => { @@ -1243,6 +1258,9 @@ const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); + const rawThreadInfo = useSelector( + state => state.threadStore.threadInfos[props.threadInfo.id], + ); const callJoinThread = useJoinThread(); const { getChatMentionSearchIndex } = useChatMentionContext(); @@ -1340,6 +1358,7 @@ CalendarQuery, +isThreadActive: boolean, +dispatchActionPromise: DispatchActionPromise, - +joinThread: (request: ClientThreadJoinRequest) => Promise, + +joinThread: (input: UseJoinThreadInput) => Promise, +typeaheadMatchedStrings: ?TypeaheadMatchedStrings, +suggestions: $ReadOnlyArray, +parentThreadInfo: ?ThreadInfo, @@ -534,18 +536,31 @@ }; async joinAction(): Promise { - const query = this.props.calendarQuery(); - return await this.props.joinThread({ - threadID: this.props.threadInfo.id, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [this.props.threadInfo.id] }, - ], - }, - }); + let joinThreadInput; + if (this.props.rawThreadInfo.thick) { + joinThreadInput = { + thick: true, + rawThreadInfo: this.props.rawThreadInfo, + }; + } else { + const query = this.props.calendarQuery(); + joinThreadInput = { + thick: false, + request: { + threadID: this.props.threadInfo.id, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [this.props.threadInfo.id] }, + ], + }, + }, + }; + } + + return await this.props.joinThread(joinThreadInput); } } @@ -573,6 +588,9 @@ const threadCreationInProgress = createThreadLoadingStatus === 'loading'; const calendarQuery = useSelector(nonThreadCalendarQuery); const dispatchActionPromise = useDispatchActionPromise(); + const rawThreadInfo = useSelector( + state => state.threadStore.threadInfos[props.threadInfo.id], + ); const callJoinThread = useJoinThread(); const { getChatMentionSearchIndex } = useChatMentionContext(); const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo); @@ -672,6 +690,7 @@