import { proteus as ProtobufOTR } from '@wireapp/protocol-messaging/web/otr' ; import type { AxiosError , AxiosRequestConfig } from 'axios' ; import { ValidationError } from '../validation/' ; import { DefaultConversationRoleName } from './ConversationRole' ; import type { ClientMismatch , Conversation , ConversationCode , ConversationIds , ConversationRolesList , Conversations , Invite , Member , MessageSendingStatus , NewConversation , NewOTRMessage , QualifiedConversationIds , RemoteConversations , } from './' ; import type { ConversationAccessUpdateEvent , ConversationCodeDeleteEvent , ConversationCodeUpdateEvent , ConversationEvent , ConversationMemberJoinEvent , ConversationMemberLeaveEvent , ConversationMessageTimerUpdateEvent , ConversationReceiptModeUpdateEvent , ConversationRenameEvent , } from '../event/' ; import { BackendError , BackendErrorLabel , HttpClient } from '../http/' ; import type { ConversationAccessUpdateData , ConversationJoinData , ConversationMemberUpdateData , ConversationMessageTimerUpdateData , ConversationNameUpdateData , ConversationOtherMemberUpdateData , ConversationReceiptModeUpdateData , ConversationTypingData , } from './data' ; import { ConversationFullError , ConversationCodeNotFoundError , ConversationLegalholdMissingConsentError , } from './ConversationError' ; import { QualifiedId } from '../user' ; export class ConversationAPI { public static readonly MAX_CHUNK_SIZE = 500 ; public static readonly URL = { ACCESS : 'access' , BOTS : 'bots' , CLIENTS : '/clients' , CODE : 'code' , CODE_CHECK : '/code-check' , CONVERSATIONS : '/conversations' , IDS : 'ids' , JOIN : '/join' , LIST : 'list' , LIST_IDS : 'list-ids' , MEMBERS : 'members' , MESSAGE_TIMER : 'message-timer' , MESSAGES : 'messages' , NAME : 'name' , OTR : 'otr' , PROTEUS : 'proteus' , RECEIPT_MODE : 'receipt-mode' , ROLES : 'roles' , SELF : 'self' , TYPING : 'typing' , V2 : 'v2' , } ; constructor ( private readonly client ) { } param conversationId see https public async deleteConversationCode ( conversationId ) { const config = { method : 'delete' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . CODE }` , } ; const response = await this . client . sendJSON < ConversationCodeDeleteEvent > ( config ) ; return response . data ; } deprecated public deleteBot ( conversationId , serviceId ) { return this . deleteService ( conversationId , serviceId ) ; } param conversationId param serviceId public async deleteService ( conversationId , serviceId ) { const config = { method : 'delete' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . BOTS }/${ serviceId }` , } ; await this . client . sendJSON ( config ) ; } param conversationId param userId see https public async deleteMember ( conversationId , userId ) { const config = { method : 'delete' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MEMBERS }/${ userId }` , } ; const response = await this . client . sendJSON < ConversationMemberLeaveEvent > ( config ) ; return response . data ; } param conversation param user public async deleteQualifiedMember ( conversation , user ) { const config = { method : 'delete' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversation . domain }/${ conversation . id }/${ ConversationAPI . URL . MEMBERS }/${ user . domain }/${ user . id }` , } ; const response = await this . client . sendJSON < ConversationMemberLeaveEvent > ( config ) ; return response . data ; } deprecated public getAllConversations ( ) { let allConversations = [ ] ; const getConversationChunks = async ( conversationId ? ) : Promise < Conversation [ ] > => { const { conversations , has_more } = await this . getConversations ( conversationId , ConversationAPI . MAX_CHUNK_SIZE ) ; if ( conversations . length ) { allConversations = allConversations . concat ( conversations ) ; } if ( has_more ) { const lastConversation = conversations . pop ( ) ; if ( lastConversation ) { return getConversationChunks ( lastConversation . id ) ; } } return allConversations ; } ; return getConversationChunks ( ) ; } param conversationId see https public async getConversationCode ( conversationId ) { const config = { method : 'GET' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . CODE }` , } ; const response = await this . client . sendJSON < ConversationCode > ( config ) ; return response . data ; } public async getConversation ( conversationId , useFederation ? ) ; public async getConversation ( conversationId , useFederation ) ; public async getConversation ( conversationId , useFederation = false ) { if ( useFederation ) { return this . getConversation_v2 ( conversationId as QualifiedId ) ; } return this . getConversation_v1 ( conversationId as string ) ; } param conversationId see https async getConversation_v1 ( conversationId ) { const url = `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }` ; const config = { method : 'get' , url , } ; const response = await this . client . sendJSON < Conversation > ( config ) ; return response . data ; } async getConversation_v2 ( conversationId ) { const { id , domain } = conversationId ; const url = `${ ConversationAPI . URL . CONVERSATIONS }/${ domain }/${ id }` ; const config = { method : 'get' , url , } ; const response = await this . client . sendJSON < Conversation > ( config ) ; return response . data ; } param limit public async getQualifiedConversationIds ( limit = 0 ) { const config = { data : { } , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ ConversationAPI . URL . LIST_IDS }` , } ; if ( limit > 0 ) { config . data . size = limit ; } const allConversations = [ ] ; const getConversationChunks = async ( pagingState ? ) : Promise < QualifiedId [ ] > => { if ( pagingState ) { config . data . paging_state = pagingState ; } const { data } = await this . client . sendJSON < QualifiedConversationIds > ( config ) ; const { qualified_conversations , has_more , paging_state } = data ; allConversations . push ( ... qualified_conversations ) ; if ( has_more ) { return getConversationChunks ( paging_state ) ; } return allConversations ; } ; return getConversationChunks ( ) ; } param limit param conversationId see https deprecated public async getConversationIds ( limit , conversationId ? ) { const config = { method : 'get' , params : { size : limit , start : conversationId , } , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ ConversationAPI . URL . IDS }` , } ; const response = await this . client . sendJSON < ConversationIds > ( config ) ; return response . data ; } param startConversationId param limit see https public getConversations ( startConversationId ? , limit = ConversationAPI . MAX_CHUNK_SIZE ) { return this . _getConversations ( startConversationId , undefined , limit ) ; } see https public async getConversationsByQualifiedIds ( conversations ) { const config = { data : { qualified_ids : conversations } , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ ConversationAPI . URL . LIST }/${ ConversationAPI . URL . V2 }` , } ; const { data } = await this . client . sendJSON < RemoteConversations > ( config ) ; return data ; } /**
     * Get all local & remote conversations from a federated backend.
     */ public async getConversationList ( ) { const allConversationIds = await this . getQualifiedConversationIds ( ) ; const conversations = await this . getConversationsByQualifiedIds ( allConversationIds ) ; return conversations . found || [ ] ; } param conversationId see https public async getConversationsByIds ( filteredConversationIds ) { let allConversations = [ ] ; const getConversationChunk = async ( chunkedConversationIds ) : Promise < Conversation [ ] > => { const { conversations } = await this . _getConversations ( undefined , chunkedConversationIds , ConversationAPI . MAX_CHUNK_SIZE ) ; return conversations ; } ; for ( let index = 0 ; index < filteredConversationIds . length ; index += ConversationAPI . MAX_CHUNK_SIZE ) { const requestChunk = filteredConversationIds . slice ( index , index + ConversationAPI . MAX_CHUNK_SIZE ) ; if ( requestChunk . length ) { const conversationChunk = await getConversationChunk ( requestChunk ) ; if ( conversationChunk . length ) { allConversations = allConversations . concat ( conversationChunk ) ; } } } return allConversations ; } param startConversationId param filteredConversationIds param limit see https private async _getConversations ( startConversationId ? , filteredConversationIds ? , limit = ConversationAPI . MAX_CHUNK_SIZE ) { const config = { method : 'get' , params : { size : limit , start : startConversationId , } , url : ConversationAPI . URL . CONVERSATIONS , } ; if ( filteredConversationIds ) { config . params . ids = filteredConversationIds . join ( ',' ) ; } const response = await this . client . sendJSON < Conversations > ( config ) ; return response . data ; } param conversationId see https public async getRoles ( conversationId ) { const config = { method : 'get' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . ROLES }` , } ; const response = await this . client . sendJSON < ConversationRolesList > ( config ) ; return response . data ; } param conversationId see https public async getMembershipProperties ( conversationId ) { const config = { method : 'get' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/self` , } ; const response = await this . client . sendJSON < Member > ( config ) ; return response . data ; } param conversationData see https public async post1to1 ( conversationData ) { const config = { data : conversationData , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/one2one` , } ; await this . client . sendJSON ( config ) ; } param conversationId param invitationData see https public async postAddMembers ( conversationId , invitationData ) { const config = { data : invitationData , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MEMBERS }` , } ; try { const response = await this . client . sendJSON < ConversationEvent > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . LEGAL_HOLD_MISSING_CONSENT : { throw new ConversationLegalholdMissingConsentError ( backendError . message ) ; } } throw error ; } } deprecated public postBot ( conversationId , providerId , serviceId ) { return this . postService ( conversationId , providerId , serviceId ) ; } param conversationId param providerId param serviceId public async postService ( conversationId , providerId , serviceId ) { const config = { data : { provider : providerId , service : serviceId , } , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . BOTS }` , } ; const response = await this . client . sendJSON < ConversationMemberJoinEvent > ( config ) ; return response . data ; } param conversationData see https public async postConversation ( conversationData ) { const config = { data : conversationData , method : 'post' , url : ConversationAPI . URL . CONVERSATIONS , } ; try { const response = await this . client . sendJSON < Conversation > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . LEGAL_HOLD_MISSING_CONSENT : { throw new ConversationLegalholdMissingConsentError ( backendError . message ) ; } } throw error ; } } param conversationId see https public async postConversationCodeRequest ( conversationId ) { const config = { method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . CODE }` , } ; const response = await this . client . sendJSON < ConversationCodeUpdateEvent > ( config ) ; return response . data ; } param conversationCode see https public async postConversationCodeCheck ( conversationCode ) { const config = { data : conversationCode , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }${ ConversationAPI . URL . CODE_CHECK }` , } ; try { await this . client . sendJSON ( config ) ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . NO_CONVERSATION_CODE : { throw new ConversationCodeNotFoundError ( backendError . message ) ; } } throw error ; } } param conversationCode see https public async postJoinByCode ( conversationCode ) { const config = { data : conversationCode , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }${ ConversationAPI . URL . JOIN }` , } ; try { const response = await this . client . sendJSON < ConversationMemberJoinEvent > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . NO_CONVERSATION_CODE : { throw new ConversationCodeNotFoundError ( backendError . message ) ; } case BackendErrorLabel . TOO_MANY_MEMBERS : { throw new ConversationFullError ( backendError . message ) ; } } throw error ; } } param conversationCode see https public async getJoinByCode ( conversationCode ) { const config = { params : conversationCode , method : 'get' , url : `${ ConversationAPI . URL . CONVERSATIONS }${ ConversationAPI . URL . JOIN }` , } ; try { const response = await this . client . sendJSON < ConversationJoinData > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . NO_CONVERSATION_CODE : { throw new ConversationCodeNotFoundError ( backendError . message ) ; } } throw error ; } } param conversationId see https public async postJoin ( conversationId ) { const config = { method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }` , } ; const response = await this . client . sendJSON < ConversationEvent > ( config ) ; return response . data ; } param sendingClientId param conversationId param messageData param ignoreMissing see https public async postOTRMessage ( sendingClientId , conversationId , messageData ? , ignoreMissing ? ) { if ( ! sendingClientId ) { throw new ValidationError ( 'Unable to send OTR message without client ID.' ) ; } messageData ||= { recipients : { } , sender : sendingClientId , } ; const config = { data : messageData , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . OTR }/${ ConversationAPI . URL . MESSAGES }` , } ; if ( typeof ignoreMissing !== 'undefined' ) { const ignore_missing = Array . isArray ( ignoreMissing ) ? ignoreMissing . join ( ',' ) : ignoreMissing ; config . params = { ignore_missing } ; delete messageData . report_missing ; } else if ( typeof messageData . report_missing === 'undefined' || ! messageData . report_missing . length ) { config . params = { ignore_missing : ! ! messageData . data } ; } const response = await this . client . sendJSON < ClientMismatch > ( config , true ) ; return response . data ; } see https public async postOTRMessageV2 ( conversationId , domain , messageData ) { const config = { data : ProtobufOTR . QualifiedNewOtrMessage . encode ( messageData ) . finish ( ) . slice ( ) , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ domain }/${ conversationId }/${ ConversationAPI . URL . PROTEUS }/${ ConversationAPI . URL . MESSAGES }` , } ; const response = await this . client . sendProtocolBuffer < MessageSendingStatus > ( config , true ) ; return response . data ; } param sendingClientId param conversationId param messageData param ignoreMissing see https public async postOTRProtobufMessage ( sendingClientId , conversationId , messageData , ignoreMissing ? ) { if ( ! sendingClientId ) { throw new ValidationError ( 'Unable to send OTR message without client ID.' ) ; } const config = { data : ProtobufOTR . NewOtrMessage . encode ( messageData ) . finish ( ) . slice ( ) , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . OTR }/${ ConversationAPI . URL . MESSAGES }` , } ; if ( typeof ignoreMissing !== 'undefined' ) { const ignore_missing = Array . isArray ( ignoreMissing ) ? ignoreMissing . join ( ',' ) : ignoreMissing ; config . params = { ignore_missing } ; messageData . reportMissing = [ ] ; } else if ( typeof messageData . reportMissing === 'undefined' || ! messageData . reportMissing . length ) { config . params = { ignore_missing : ! ! messageData . blob } ; } const response = await this . client . sendProtocolBuffer < ClientMismatch > ( config , true ) ; return response . data ; } public async postForClients ( clientId , conversationId ) { try { await this . postOTRMessage ( clientId , conversationId ) ; throw new Error ( `Expected backend to throw error.` ) ; } catch ( error ) { return ( error as AxiosError ) . response ! . data ; } } see https public async postSelf ( ) { const config = { method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ ConversationAPI . URL . SELF }` , } ; const response = await this . client . sendJSON < Conversation > ( config ) ; return response . data ; } param conversationId param typingData see https public async postTyping ( conversationId , typingData ) { const config = { data : typingData , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . TYPING }` , } ; await this . client . sendJSON ( config ) ; } param conversationId param accessData see https public async putAccess ( conversationId , accessData ) { const config = { data : accessData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . ACCESS }` , } ; const response = await this . client . sendJSON < ConversationAccessUpdateEvent > ( config ) ; return response . data ; } param conversationId param conversationNameData see https public async putConversation ( conversationId , conversationNameData ) { const config = { data : conversationNameData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . NAME }` , } ; const response = await this . client . sendJSON < ConversationRenameEvent > ( config ) ; return response . data ; } param conversationId param conversationData see https public async putConversationMessageTimer ( conversationId , messageTimerData ) { const config = { data : messageTimerData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MESSAGE_TIMER }` , } ; const response = await this . client . sendJSON < ConversationMessageTimerUpdateEvent > ( config ) ; return response . data ; } param conversationId param receiptModeData see https public async putConversationReceiptMode ( conversationId , receiptModeData ) { const config = { data : receiptModeData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . RECEIPT_MODE }` , } ; const response = await this . client . sendJSON < ConversationReceiptModeUpdateEvent > ( config ) ; return response . data ; } param conversationId param userIds see https public async postMembers ( conversationId , userIds ) { const config = { data : { conversation_role : DefaultConversationRoleName . WIRE_MEMBER , users : userIds , } , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MEMBERS }` , } ; try { const response = await this . client . sendJSON < ConversationMemberJoinEvent > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . LEGAL_HOLD_MISSING_CONSENT : { throw new ConversationLegalholdMissingConsentError ( backendError . message ) ; } } throw error ; } } param conversationId param users public async postMembersV2 ( conversationId , users ) { const config = { data : { conversation_role : DefaultConversationRoleName . WIRE_MEMBER , qualified_users : users , } , method : 'post' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MEMBERS }/${ ConversationAPI . URL . V2 }` , } ; try { const response = await this . client . sendJSON < ConversationMemberJoinEvent > ( config ) ; return response . data ; } catch ( error ) { const backendError = error as BackendError ; switch ( backendError . label ) { case BackendErrorLabel . LEGAL_HOLD_MISSING_CONSENT : { throw new ConversationLegalholdMissingConsentError ( backendError . message ) ; } } throw error ; } } param userId param conversationId param memberUpdateData see https public async putOtherMember ( userId , conversationId , memberUpdateData ) { const config = { data : memberUpdateData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . MEMBERS }/${ userId }` , } ; await this . client . sendJSON < ConversationMemberJoinEvent > ( config ) ; } param conversationId param memberData see https public async putMembershipProperties ( conversationId , memberData ) { const config = { data : memberData , method : 'put' , url : `${ ConversationAPI . URL . CONVERSATIONS }/${ conversationId }/${ ConversationAPI . URL . SELF }` , } ; await this . client . sendJSON ( config ) ; } }