diff --git a/lib/hooks/input-state-container-hooks.js b/lib/hooks/input-state-container-hooks.js --- a/lib/hooks/input-state-container-hooks.js +++ b/lib/hooks/input-state-container-hooks.js @@ -24,6 +24,7 @@ } from '../media/media-utils.js'; import { dmOperationSpecificationTypes } from '../shared/dm-ops/dm-op-types.js'; import { useSendComposableDMOperation } from '../shared/dm-ops/process-dm-ops.js'; +import { threadSpecs } from '../shared/threads/thread-specs.js'; import type { BlobOperation } from '../types/holder-types.js'; import type { EncryptedImage, @@ -62,83 +63,29 @@ parentThreadInfo: ?ThreadInfo, sidebarCreation: boolean, ) => Promise { - const sendTextMessage = useSendTextMessage(); + const sendKeyserverTextMessage = useSendTextMessage(); const sendComposableDMOperation = useSendComposableDMOperation(); return React.useCallback( - async ( + ( messageInfo: RawTextMessageInfo, threadInfo: ThreadInfo, parentThreadInfo: ?ThreadInfo, sidebarCreation: boolean, - ) => { - const { localID } = messageInfo; - invariant( - localID !== null && localID !== undefined, - 'localID should be set', - ); - if (!threadTypeIsThick(threadInfo.type)) { - const result = await sendTextMessage({ - threadID: messageInfo.threadID, - localID, - text: messageInfo.text, + ) => + threadSpecs[threadInfo.type].protocol.sendTextMessage( + { + messageInfo, + threadInfo, + parentThreadInfo, sidebarCreation, - }); - return { - localID, - serverID: result.id, - threadID: messageInfo.threadID, - time: result.time, - }; - } - - const messageID = uuid.v4(); - const time = Date.now(); - - const recipients = - threadInfo.type === thickThreadTypes.THICK_SIDEBAR && parentThreadInfo - ? parentThreadInfo.members - : threadInfo.members; - const recipientsIDs = recipients.map(recipient => recipient.id); - - const result = await sendComposableDMOperation({ - type: dmOperationSpecificationTypes.OUTBOUND, - op: { - type: 'send_text_message', - threadID: threadInfo.id, - creatorID: messageInfo.creatorID, - time, - messageID, - text: messageInfo.text, }, - // We need to use a different mechanism than `all_thread_members` - // because when creating a thread, the thread might not yet - // be in the store. - recipients: { - type: 'some_users', - userIDs: recipientsIDs, + { + sendKeyserverTextMessage, + sendComposableDMOperation, }, - sendOnly: true, - composableMessageID: localID, - }); - - if (result.result === 'failure' && result.failedMessageIDs.length > 0) { - const error = new SendMessageError( - 'Failed to send message to all peers', - localID, - messageInfo.threadID, - ); - error.failedOutboundP2PMessageIDs = result.failedMessageIDs; - throw error; - } - return { - localID, - serverID: messageID, - threadID: messageInfo.threadID, - time, - }; - }, - [sendComposableDMOperation, sendTextMessage], + ), + [sendComposableDMOperation, sendKeyserverTextMessage], ); } diff --git a/lib/shared/threads/community-announcement-root-spec.js b/lib/shared/threads/community-announcement-root-spec.js --- a/lib/shared/threads/community-announcement-root-spec.js +++ b/lib/shared/threads/community-announcement-root-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communityAnnouncementRootSpec: ThreadSpec = Object.freeze({ traits: new Set(['community', 'announcement']), + protocol: keyserverThreadProtocol, }); export { communityAnnouncementRootSpec }; diff --git a/lib/shared/threads/community-open-announcement-subthread-spec.js b/lib/shared/threads/community-open-announcement-subthread-spec.js --- a/lib/shared/threads/community-open-announcement-subthread-spec.js +++ b/lib/shared/threads/community-open-announcement-subthread-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communityOpenAnnouncementSubthreadSpec: ThreadSpec = Object.freeze({ traits: new Set(['communitySubthread', 'announcement']), + protocol: keyserverThreadProtocol, }); export { communityOpenAnnouncementSubthreadSpec }; diff --git a/lib/shared/threads/community-open-subthread-spec.js b/lib/shared/threads/community-open-subthread-spec.js --- a/lib/shared/threads/community-open-subthread-spec.js +++ b/lib/shared/threads/community-open-subthread-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communityOpenSubthreadSpec: ThreadSpec = Object.freeze({ traits: new Set(['communitySubthread']), + protocol: keyserverThreadProtocol, }); export { communityOpenSubthreadSpec }; diff --git a/lib/shared/threads/community-root-spec.js b/lib/shared/threads/community-root-spec.js --- a/lib/shared/threads/community-root-spec.js +++ b/lib/shared/threads/community-root-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communityRootSpec: ThreadSpec = Object.freeze({ traits: new Set(['community']), + protocol: keyserverThreadProtocol, }); export { communityRootSpec }; diff --git a/lib/shared/threads/community-secret-announcement-subthread-spec.js b/lib/shared/threads/community-secret-announcement-subthread-spec.js --- a/lib/shared/threads/community-secret-announcement-subthread-spec.js +++ b/lib/shared/threads/community-secret-announcement-subthread-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communitySecretAnnouncementSubthreadSpec: ThreadSpec = Object.freeze({ traits: new Set(['communitySubthread', 'announcement']), + protocol: keyserverThreadProtocol, }); export { communitySecretAnnouncementSubthreadSpec }; diff --git a/lib/shared/threads/community-secret-subthread-spec.js b/lib/shared/threads/community-secret-subthread-spec.js --- a/lib/shared/threads/community-secret-subthread-spec.js +++ b/lib/shared/threads/community-secret-subthread-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const communitySecretSubthreadSpec: ThreadSpec = Object.freeze({ traits: new Set(['communitySubthread']), + protocol: keyserverThreadProtocol, }); export { communitySecretSubthreadSpec }; diff --git a/lib/shared/threads/genesis-personal-spec.js b/lib/shared/threads/genesis-personal-spec.js --- a/lib/shared/threads/genesis-personal-spec.js +++ b/lib/shared/threads/genesis-personal-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const genesisPersonalSpec: ThreadSpec = Object.freeze({ traits: new Set(['personal']), + protocol: keyserverThreadProtocol, }); export { genesisPersonalSpec }; diff --git a/lib/shared/threads/genesis-private-spec.js b/lib/shared/threads/genesis-private-spec.js --- a/lib/shared/threads/genesis-private-spec.js +++ b/lib/shared/threads/genesis-private-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const genesisPrivateSpec: ThreadSpec = Object.freeze({ traits: new Set(['private']), + protocol: keyserverThreadProtocol, }); export { genesisPrivateSpec }; diff --git a/lib/shared/threads/genesis-spec.js b/lib/shared/threads/genesis-spec.js --- a/lib/shared/threads/genesis-spec.js +++ b/lib/shared/threads/genesis-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const genesisSpec: ThreadSpec = Object.freeze({ traits: new Set(['community', 'announcement']), + protocol: keyserverThreadProtocol, }); export { genesisSpec }; diff --git a/lib/shared/threads/local-spec.js b/lib/shared/threads/local-spec.js --- a/lib/shared/threads/local-spec.js +++ b/lib/shared/threads/local-spec.js @@ -1,9 +1,11 @@ // @flow +import { dmThreadProtocol } from './protocols/dm-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const localSpec: ThreadSpec = Object.freeze({ traits: new Set(), + protocol: dmThreadProtocol, }); export { localSpec }; diff --git a/lib/shared/threads/personal-spec.js b/lib/shared/threads/personal-spec.js --- a/lib/shared/threads/personal-spec.js +++ b/lib/shared/threads/personal-spec.js @@ -1,9 +1,11 @@ // @flow +import { dmThreadProtocol } from './protocols/dm-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const personalSpec: ThreadSpec = Object.freeze({ traits: new Set(['personal']), + protocol: dmThreadProtocol, }); export { personalSpec }; diff --git a/lib/shared/threads/private-spec.js b/lib/shared/threads/private-spec.js --- a/lib/shared/threads/private-spec.js +++ b/lib/shared/threads/private-spec.js @@ -1,9 +1,11 @@ // @flow +import { dmThreadProtocol } from './protocols/dm-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const privateSpec: ThreadSpec = Object.freeze({ traits: new Set(['private']), + protocol: dmThreadProtocol, }); export { privateSpec }; diff --git a/lib/shared/threads/protocols/dm-thread-protocol.js b/lib/shared/threads/protocols/dm-thread-protocol.js new file mode 100644 --- /dev/null +++ b/lib/shared/threads/protocols/dm-thread-protocol.js @@ -0,0 +1,75 @@ +// @flow + +import invariant from 'invariant'; +import uuid from 'uuid'; + +import { thickThreadTypes } from '../../../types/thread-types-enum.js'; +import { SendMessageError } from '../../../utils/errors.js'; +import { dmOperationSpecificationTypes } from '../../dm-ops/dm-op-types.js'; +import { + type ProtocolSendTextMessageInput, + type SendTextMessageUtils, + type ThreadProtocol, +} from '../thread-spec.js'; + +const dmThreadProtocol: ThreadProtocol = Object.freeze({ + sendTextMessage: async ( + message: ProtocolSendTextMessageInput, + utils: SendTextMessageUtils, + ) => { + const { messageInfo, threadInfo, parentThreadInfo } = message; + const { localID } = messageInfo; + invariant( + localID !== null && localID !== undefined, + 'localID should be set', + ); + + const messageID = uuid.v4(); + const time = Date.now(); + + const recipients = + threadInfo.type === thickThreadTypes.THICK_SIDEBAR && parentThreadInfo + ? parentThreadInfo.members + : threadInfo.members; + const recipientsIDs = recipients.map(recipient => recipient.id); + + const result = await utils.sendComposableDMOperation({ + type: dmOperationSpecificationTypes.OUTBOUND, + op: { + type: 'send_text_message', + threadID: threadInfo.id, + creatorID: messageInfo.creatorID, + time, + messageID, + text: messageInfo.text, + }, + // We need to use a different mechanism than `all_thread_members` + // because when creating a thread, the thread might not yet + // be in the store. + recipients: { + type: 'some_users', + userIDs: recipientsIDs, + }, + sendOnly: true, + composableMessageID: localID, + }); + + if (result.result === 'failure' && result.failedMessageIDs.length > 0) { + const error = new SendMessageError( + 'Failed to send message to all peers', + localID, + messageInfo.threadID, + ); + error.failedOutboundP2PMessageIDs = result.failedMessageIDs; + throw error; + } + return { + localID, + serverID: messageID, + threadID: messageInfo.threadID, + time, + }; + }, +}); + +export { dmThreadProtocol }; diff --git a/lib/shared/threads/protocols/keyserver-thread-protocol.js b/lib/shared/threads/protocols/keyserver-thread-protocol.js new file mode 100644 --- /dev/null +++ b/lib/shared/threads/protocols/keyserver-thread-protocol.js @@ -0,0 +1,37 @@ +// @flow + +import invariant from 'invariant'; + +import { + type ThreadProtocol, + type ProtocolSendTextMessageInput, + type SendTextMessageUtils, +} from '../thread-spec.js'; + +const keyserverThreadProtocol: ThreadProtocol = Object.freeze({ + sendTextMessage: async ( + message: ProtocolSendTextMessageInput, + utils: SendTextMessageUtils, + ) => { + const { messageInfo, sidebarCreation } = message; + const { localID } = messageInfo; + invariant( + localID !== null && localID !== undefined, + 'localID should be set', + ); + const result = await utils.sendKeyserverTextMessage({ + threadID: messageInfo.threadID, + localID, + text: messageInfo.text, + sidebarCreation, + }); + return { + localID, + serverID: result.id, + threadID: messageInfo.threadID, + time: result.time, + }; + }, +}); + +export { keyserverThreadProtocol }; diff --git a/lib/shared/threads/sidebar-spec.js b/lib/shared/threads/sidebar-spec.js --- a/lib/shared/threads/sidebar-spec.js +++ b/lib/shared/threads/sidebar-spec.js @@ -1,9 +1,11 @@ // @flow +import { keyserverThreadProtocol } from './protocols/keyserver-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const sidebarSpec: ThreadSpec = Object.freeze({ traits: new Set(['sidebar']), + protocol: keyserverThreadProtocol, }); export { sidebarSpec }; diff --git a/lib/shared/threads/thick-sidebar-spec.js b/lib/shared/threads/thick-sidebar-spec.js --- a/lib/shared/threads/thick-sidebar-spec.js +++ b/lib/shared/threads/thick-sidebar-spec.js @@ -1,9 +1,11 @@ // @flow +import { dmThreadProtocol } from './protocols/dm-thread-protocol.js'; import type { ThreadSpec } from './thread-spec.js'; const thickSidebarSpec: ThreadSpec = Object.freeze({ traits: new Set(['sidebar']), + protocol: dmThreadProtocol, }); export { thickSidebarSpec }; diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js --- a/lib/shared/threads/thread-spec.js +++ b/lib/shared/threads/thread-spec.js @@ -1,5 +1,15 @@ // @flow +import type { SendTextMessageInput } from '../../actions/message-actions'; +import type { ProcessOutboundP2PMessagesResult } from '../../tunnelbroker/peer-to-peer-context.js'; +import type { + SendMessageResult, + SendMessagePayload, +} from '../../types/message-types'; +import type { RawTextMessageInfo } from '../../types/messages/text.js'; +import type { ThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; +import type { OutboundComposableDMOperationSpecification } from '../dm-ops/dm-op-types.js'; + export type ThreadTrait = | 'sidebar' | 'community' @@ -8,6 +18,25 @@ | 'private' | 'communitySubthread'; +export type ProtocolSendTextMessageInput = { + +messageInfo: RawTextMessageInfo, + +threadInfo: ThreadInfo, + +parentThreadInfo: ?ThreadInfo, + +sidebarCreation: boolean, +}; +export type SendTextMessageUtils = { + +sendKeyserverTextMessage: SendTextMessageInput => Promise, + +sendComposableDMOperation: OutboundComposableDMOperationSpecification => Promise, +}; + +export type ThreadProtocol = { + +sendTextMessage: ( + message: ProtocolSendTextMessageInput, + utils: SendTextMessageUtils, + ) => Promise, +}; + export type ThreadSpec = { +traits: $ReadOnlySet, + +protocol: ThreadProtocol, };