diff --git a/packages/api-client/CHANGELOG.md b/packages/api-client/CHANGELOG.md index 6a4e8e0b31..31c88c9695 100644 --- a/packages/api-client/CHANGELOG.md +++ b/packages/api-client/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [27.32.0](https://github.com/wireapp/wire-web-packages/compare/@wireapp/api-client@27.31.0...@wireapp/api-client@27.32.0) (2025-03-26) + +### Features + +* update feature config and POST /conversations payload for channels [WPB-15758] ([#6991](https://github.com/wireapp/wire-web-packages/issues/6991)) ([025f02e](https://github.com/wireapp/wire-web-packages/commit/025f02ecd47c0a28cbec24f1f41002c38dbaefda)) + +# [27.31.0](https://github.com/wireapp/wire-web-packages/compare/@wireapp/api-client@27.30.0...@wireapp/api-client@27.31.0) (2025-03-25) + +### Features + +* **api-client/cells:** add "offset" and "limit" configuration ([#6990](https://github.com/wireapp/wire-web-packages/issues/6990)) ([d7ed523](https://github.com/wireapp/wire-web-packages/commit/d7ed5231a1322d8da500b0a009e4e07ff2ba47ef)) + +# [27.30.0](https://github.com/wireapp/wire-web-packages/compare/@wireapp/api-client@27.29.1...@wireapp/api-client@27.30.0) (2025-03-24) + +### Features + +* Update verify domain registration api path ([#6989](https://github.com/wireapp/wire-web-packages/issues/6989)) ([65277e4](https://github.com/wireapp/wire-web-packages/commit/65277e456b9f32a06b8714f998981742a29d6fec)) + +## [27.29.1](https://github.com/wireapp/wire-web-packages/compare/@wireapp/api-client@27.29.0...@wireapp/api-client@27.29.1) (2025-03-20) + +### Bug Fixes + +* **api-client/cells:** single cells initialization ([#6988](https://github.com/wireapp/wire-web-packages/issues/6988)) ([52ff551](https://github.com/wireapp/wire-web-packages/commit/52ff55162cc65d5d8df1c7e66e961611e80ddd5d)) + # [27.29.0](https://github.com/wireapp/wire-web-packages/compare/@wireapp/api-client@27.28.0...@wireapp/api-client@27.29.0) (2025-03-20) ### Features diff --git a/packages/api-client/package.json b/packages/api-client/package.json index 2573fa18e7..9e7ce3a5cc 100644 --- a/packages/api-client/package.json +++ b/packages/api-client/package.json @@ -69,6 +69,6 @@ "watch": "webpack serve --config webpack.browser.js", "prepare": "yarn dist" }, - "version": "27.29.0", + "version": "27.32.0", "gitHead": "5339f01fe01ef0871da8c8bc8662fbe9e604754a" } diff --git a/packages/api-client/src/APIClient.ts b/packages/api-client/src/APIClient.ts index 3c648376ac..e2df68b682 100644 --- a/packages/api-client/src/APIClient.ts +++ b/packages/api-client/src/APIClient.ts @@ -154,6 +154,7 @@ export class APIClient extends EventEmitter { // APIs public api: Apis; + private cellsApi: CellsAPI | null = null; // Configuration private readonly accessTokenStore: AccessTokenStore; @@ -208,16 +209,21 @@ export class APIClient extends EventEmitter { const assetAPI = new AssetAPI(this.transport.http, backendFeatures); + // Prevents the CellsAPI from being initialized multiple times + if (!this.cellsApi) { + this.cellsApi = new CellsAPI({ + httpClientConfig: this.config, + accessTokenStore: this.accessTokenStore, + }); + } + return { account: new AccountAPI(this.transport.http), asset: assetAPI, auth: new AuthAPI(this.transport.http), services: new ServicesAPI(this.transport.http, assetAPI), broadcast: new BroadcastAPI(this.transport.http), - cells: new CellsAPI({ - httpClientConfig: this.config, - accessTokenStore: this.accessTokenStore, - }), + cells: this.cellsApi, client: new ClientAPI(this.transport.http), connection: new ConnectionAPI(this.transport.http), conversation: new ConversationAPI(this.transport.http, backendFeatures), diff --git a/packages/api-client/src/cells/CellsAPI.test.ts b/packages/api-client/src/cells/CellsAPI.test.ts index 8bf746264b..99ebdadf79 100644 --- a/packages/api-client/src/cells/CellsAPI.test.ts +++ b/packages/api-client/src/cells/CellsAPI.test.ts @@ -302,7 +302,6 @@ describe('CellsAPI', () => { describe('getAllFiles', () => { it('retrieves all files with the correct parameters', async () => { const mockCollection: Partial = { - // Use appropriate properties based on the actual RestNodeCollection interface Nodes: [ {Path: '/file1.txt', Uuid: 'uuid1'}, {Path: '/file2.txt', Uuid: 'uuid2'}, @@ -315,7 +314,55 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Locators: {Many: [{Path: `${TEST_FILE_PATH}/*`}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', + }); + expect(result).toEqual(mockCollection); + }); + + it('uses default values when limit and offset are not provided', async () => { + const mockCollection: Partial = { + Nodes: [ + {Path: '/file1.txt', Uuid: 'uuid1'}, + {Path: '/file2.txt', Uuid: 'uuid2'}, + ], + }; + + mockNodeServiceApi.lookup.mockResolvedValueOnce(createMockResponse(mockCollection as RestNodeCollection)); + + const result = await cellsAPI.getAllFiles({path: TEST_FILE_PATH}); + + expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ + Locators: {Many: [{Path: `${TEST_FILE_PATH}/*`}]}, + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', + }); + expect(result).toEqual(mockCollection); + }); + + it('respects custom limit and offset parameters', async () => { + const mockCollection: Partial = { + Nodes: [ + {Path: '/file1.txt', Uuid: 'uuid1'}, + {Path: '/file2.txt', Uuid: 'uuid2'}, + ], + }; + + mockNodeServiceApi.lookup.mockResolvedValueOnce(createMockResponse(mockCollection as RestNodeCollection)); + + const result = await cellsAPI.getAllFiles({ + path: TEST_FILE_PATH, + limit: 5, + offset: 10, + }); + + expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ + Locators: {Many: [{Path: `${TEST_FILE_PATH}/*`}]}, + Flags: ['WithPreSignedURLs'], + Limit: '5', + Offset: '10', }); expect(result).toEqual(mockCollection); }); @@ -500,7 +547,7 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Locators: {Many: [{Path: filePath}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], }); expect(result).toEqual(mockNode); }); @@ -543,7 +590,7 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Locators: {Many: [{Uuid: fileUuid}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], }); expect(result).toEqual(mockNode); }); @@ -750,7 +797,69 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Query: {FileName: searchPhrase, Type: 'LEAF'}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', + }); + expect(result).toEqual(mockResponse); + }); + + it('uses default values when limit and offset are not provided', async () => { + const searchPhrase = 'test'; + const mockResponse: RestNodeCollection = { + Nodes: [ + { + Path: '/test.txt', + Uuid: 'file-uuid-1', + }, + { + Path: '/folder/test-file.txt', + Uuid: 'file-uuid-2', + }, + ], + } as RestNodeCollection; + + mockNodeServiceApi.lookup.mockResolvedValueOnce(createMockResponse(mockResponse)); + + const result = await cellsAPI.searchFiles({phrase: searchPhrase}); + + expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ + Query: {FileName: searchPhrase, Type: 'LEAF'}, + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', + }); + expect(result).toEqual(mockResponse); + }); + + it('respects custom limit and offset parameters', async () => { + const searchPhrase = 'test'; + const mockResponse: RestNodeCollection = { + Nodes: [ + { + Path: '/test.txt', + Uuid: 'file-uuid-1', + }, + { + Path: '/folder/test-file.txt', + Uuid: 'file-uuid-2', + }, + ], + } as RestNodeCollection; + + mockNodeServiceApi.lookup.mockResolvedValueOnce(createMockResponse(mockResponse)); + + const result = await cellsAPI.searchFiles({ + phrase: searchPhrase, + limit: 5, + offset: 10, + }); + + expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ + Query: {FileName: searchPhrase, Type: 'LEAF'}, + Flags: ['WithPreSignedURLs'], + Limit: '5', + Offset: '10', }); expect(result).toEqual(mockResponse); }); @@ -767,7 +876,9 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Query: {FileName: searchPhrase, Type: 'LEAF'}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', }); expect(result).toEqual(mockResponse); }); @@ -793,7 +904,9 @@ describe('CellsAPI', () => { expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({ Query: {FileName: searchPhrase, Type: 'LEAF'}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: '10', + Offset: '0', }); expect(result).toEqual(mockResponse); }); diff --git a/packages/api-client/src/cells/CellsAPI.ts b/packages/api-client/src/cells/CellsAPI.ts index f8b3243c4a..5540d98046 100644 --- a/packages/api-client/src/cells/CellsAPI.ts +++ b/packages/api-client/src/cells/CellsAPI.ts @@ -36,7 +36,9 @@ import {S3Service} from './CellsStorage/S3Service'; import {AccessTokenStore} from '../auth'; import {HttpClient} from '../http'; -const CONFIGURATION_ERROR = 'CellsAPI is not configured. Call configure() before using any methods.'; +const CONFIGURATION_ERROR = 'CellsAPI is not initialized. Call initialize() before using any methods.'; +const DEFAULT_LIMIT = 10; +const DEFAULT_OFFSET = 0; interface CellsConfig { pydio: { @@ -171,7 +173,7 @@ export class CellsAPI { const result = await this.client.lookup({ Locators: {Many: [{Path: path}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], }); const node = result.data.Nodes?.[0]; @@ -190,7 +192,7 @@ export class CellsAPI { const result = await this.client.lookup({ Locators: {Many: [{Uuid: uuid}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], }); const node = result.data.Nodes?.[0]; @@ -222,27 +224,47 @@ export class CellsAPI { return result.data; } - async getAllFiles({path}: {path: string}): Promise { + async getAllFiles({ + path, + limit = DEFAULT_LIMIT, + offset = DEFAULT_OFFSET, + }: { + path: string; + limit?: number; + offset?: number; + }): Promise { if (!this.client || !this.storageService) { throw new Error(CONFIGURATION_ERROR); } const result = await this.client.lookup({ Locators: {Many: [{Path: `${path}/*`}]}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: `${limit}`, + Offset: `${offset}`, }); return result.data; } - async searchFiles({phrase}: {phrase: string}): Promise { + async searchFiles({ + phrase, + limit = DEFAULT_LIMIT, + offset = DEFAULT_OFFSET, + }: { + phrase: string; + limit?: number; + offset?: number; + }): Promise { if (!this.client || !this.storageService) { throw new Error(CONFIGURATION_ERROR); } const result = await this.client.lookup({ Query: {FileName: phrase, Type: 'LEAF'}, - Flags: ['WithVersionsAll', 'WithPreSignedURLs'], + Flags: ['WithPreSignedURLs'], + Limit: `${limit}`, + Offset: `${offset}`, }); return result.data; diff --git a/packages/api-client/src/conversation/Conversation.ts b/packages/api-client/src/conversation/Conversation.ts index 3ed3a03660..46b2b5b2f1 100644 --- a/packages/api-client/src/conversation/Conversation.ts +++ b/packages/api-client/src/conversation/Conversation.ts @@ -53,6 +53,16 @@ export enum CONVERSATION_ACCESS { PRIVATE = 'private', } +export enum GROUP_CONVERSATION_TYPE { + CHANNEL = 'channel', + GROUP_CONVERSATION = 'group_conversation', +} + +export enum ADD_PERMISSION { + EVERYONE = 'everyone', + ADMINS = 'admins', +} + type UUID = string; /** * A conversation object as returned from the server @@ -64,7 +74,8 @@ export interface Conversation { type: CONVERSATION_TYPE; creator: UUID; access: CONVERSATION_ACCESS[]; - + group_conv_type?: GROUP_CONVERSATION_TYPE; + add_permission?: ADD_PERMISSION; /** How users can join conversations */ //CONVERSATION_ACCESS_ROLE for api <= v2, ACCESS_ROLE_V2[] since api v3 access_role: CONVERSATION_LEGACY_ACCESS_ROLE | CONVERSATION_ACCESS_ROLE[]; diff --git a/packages/api-client/src/conversation/NewConversation.ts b/packages/api-client/src/conversation/NewConversation.ts index 4c59218abf..dd23b736e5 100644 --- a/packages/api-client/src/conversation/NewConversation.ts +++ b/packages/api-client/src/conversation/NewConversation.ts @@ -31,7 +31,12 @@ export enum ConversationProtocol { } export interface NewConversation - extends Partial> { + extends Partial< + Pick< + Conversation, + 'access' | 'access_role' | 'access_role_v2' | 'message_timer' | 'name' | 'group_conv_type' | 'add_permission' + > + > { conversation_role?: DefaultConversationRoleName; qualified_users?: QualifiedId[]; receipt_mode: RECEIPT_MODE | null; diff --git a/packages/api-client/src/team/feature/Feature.ts b/packages/api-client/src/team/feature/Feature.ts index d3f9c33cea..b1e135c07f 100644 --- a/packages/api-client/src/team/feature/Feature.ts +++ b/packages/api-client/src/team/feature/Feature.ts @@ -29,6 +29,12 @@ export enum FeatureLockStatus { UNLOCKED = 'unlocked', } +export enum AccessType { + TEAM_MEMBERS = 'team-members', + EVERYONE = 'everyone', + ADMINS = 'admins', +} + export interface FeatureWithoutConfig { status: FeatureStatus; lockStatus?: FeatureLockStatus; @@ -46,6 +52,11 @@ export interface FeatureAppLockConfig extends FeatureConfig { inactivityTimeoutSecs: number; } +export interface FeatureChannelsConfig { + allowed_to_create_channels: AccessType; + allowed_to_open_channels: AccessType; +} + export enum SelfDeletingTimeout { OFF = 0, SECONDS_10 = 10, @@ -92,6 +103,7 @@ export type FeatureAppLock = Feature; export type FeatureClassifiedDomains = Feature; export type FeatureConferenceCalling = Feature; export type FeatureDigitalSignature = FeatureWithoutConfig; +export type FeatureDomainRegistration = FeatureWithoutConfig; export type FeatureConversationGuestLink = FeatureWithoutConfig; export type FeatureFileSharing = FeatureWithoutConfig; export type FeatureLegalhold = FeatureWithoutConfig; @@ -105,3 +117,4 @@ export type FeatureSSO = FeatureWithoutConfig; export type FeatureSndFactorPassword = FeatureWithoutConfig; export type FeatureValidateSAMLEmails = FeatureWithoutConfig; export type FeatureVideoCalling = FeatureWithoutConfig; +export type FeatureChannels = Feature; diff --git a/packages/api-client/src/team/feature/FeatureAPI.ts b/packages/api-client/src/team/feature/FeatureAPI.ts index 814c21668d..120cdefd0f 100644 --- a/packages/api-client/src/team/feature/FeatureAPI.ts +++ b/packages/api-client/src/team/feature/FeatureAPI.ts @@ -33,6 +33,7 @@ import { FeatureMLSE2EId, FeatureMLSMigration, FeatureDownloadPath, + FeatureDomainRegistration, } from './Feature'; import {InvalidAppLockTimeoutError} from './FeatureError'; import {FeatureList} from './FeatureList'; @@ -50,6 +51,7 @@ export class FeatureAPI { CALLING_VIDEO: 'videoCalling', SELF_DELETING_MESSAGES: 'selfDeletingMessages', DIGITAL_SIGNATURES: 'digitalSignatures', + DOMAIN_REGISTRATION: 'domainRegistration', DL_PATH: 'enforceFileDownloadLocation', CONVERSATION_GUEST_LINKS: 'conversationGuestLinks', FEATURE_CONFIGS: '/feature-configs', @@ -347,6 +349,16 @@ export class FeatureAPI { return response.data; } + public async getDomainRegistrationFeature(teamId: string): Promise { + const config: AxiosRequestConfig = { + method: 'get', + url: `${FeatureAPI.URL.TEAMS}/${teamId}/${FeatureAPI.URL.FEATURES}/${FeatureAPI.URL.DOMAIN_REGISTRATION}`, + }; + + const response = await this.client.sendJSON(config); + return response.data; + } + public async getAppLockFeature(teamId: string): Promise { const config: AxiosRequestConfig = { method: 'get', diff --git a/packages/api-client/src/team/feature/FeatureList.ts b/packages/api-client/src/team/feature/FeatureList.ts index 08394ab995..b97107bc42 100644 --- a/packages/api-client/src/team/feature/FeatureList.ts +++ b/packages/api-client/src/team/feature/FeatureList.ts @@ -19,9 +19,11 @@ import { FeatureAppLock, + FeatureChannels, FeatureClassifiedDomains, FeatureConferenceCalling, FeatureDigitalSignature, + FeatureDomainRegistration, FeatureDownloadPath, FeatureFileSharing, FeatureLegalhold, @@ -42,6 +44,7 @@ export enum FEATURE_KEY { CONFERENCE_CALLING = 'conferenceCalling', CONVERSATION_GUEST_LINKS = 'conversationGuestLinks', DIGITAL_SIGNATURES = 'digitalSignatures', + DOMAIN_REGISTRATION = 'domainRegistration', ENFORCE_DOWNLOAD_PATH = 'enforceFileDownloadLocation', FILE_SHARING = 'fileSharing', LEGALHOLD = 'legalhold', @@ -54,6 +57,7 @@ export enum FEATURE_KEY { SSO = 'sso', VALIDATE_SAML_EMAILS = 'validateSAMLemails', VIDEO_CALLING = 'videoCalling', + CHANNELS = 'channels', } export type FeatureList = { @@ -61,6 +65,7 @@ export type FeatureList = { [FEATURE_KEY.CLASSIFIED_DOMAINS]?: FeatureClassifiedDomains; [FEATURE_KEY.CONFERENCE_CALLING]?: FeatureConferenceCalling; [FEATURE_KEY.DIGITAL_SIGNATURES]?: FeatureDigitalSignature; + [FEATURE_KEY.DOMAIN_REGISTRATION]?: FeatureDomainRegistration; [FEATURE_KEY.ENFORCE_DOWNLOAD_PATH]?: FeatureDownloadPath; [FEATURE_KEY.CONVERSATION_GUEST_LINKS]?: FeatureConversationGuestLink; [FEATURE_KEY.FILE_SHARING]?: FeatureFileSharing; @@ -74,4 +79,5 @@ export type FeatureList = { [FEATURE_KEY.MLS_MIGRATION]?: FeatureMLSMigration; [FEATURE_KEY.VALIDATE_SAML_EMAILS]?: FeatureWithoutConfig; [FEATURE_KEY.VIDEO_CALLING]?: FeatureVideoCalling; + [FEATURE_KEY.CHANNELS]?: FeatureChannels; }; diff --git a/packages/api-client/src/team/sso/SSOAPI.ts b/packages/api-client/src/team/sso/SSOAPI.ts index 994a43cc09..e61adf2929 100644 --- a/packages/api-client/src/team/sso/SSOAPI.ts +++ b/packages/api-client/src/team/sso/SSOAPI.ts @@ -89,7 +89,7 @@ export class SSOAPI { challenge_token: challengeToken, }, method: 'post', - url: `${SSOAPI.URL.DOMAIN_VERIFICATION}/${domain}/${SSOAPI.URL.CHALLENGES}/${challengeId}`, + url: `${SSOAPI.URL.DOMAIN_VERIFICATION}/${domain}/${SSOAPI.URL.TEAM}/${SSOAPI.URL.CHALLENGES}/${challengeId}`, }; const response = await this.client.sendJSON(config); diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 517bbc7f34..5ef366c16f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [46.20.8](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.7...@wireapp/core@46.20.8) (2025-03-26) + +**Note:** Version bump only for package @wireapp/core + +## [46.20.7](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.6...@wireapp/core@46.20.7) (2025-03-25) + +**Note:** Version bump only for package @wireapp/core + +## [46.20.6](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.5...@wireapp/core@46.20.6) (2025-03-25) + +**Note:** Version bump only for package @wireapp/core + +## [46.20.5](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.4...@wireapp/core@46.20.5) (2025-03-24) + +**Note:** Version bump only for package @wireapp/core + +## [46.20.4](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.3...@wireapp/core@46.20.4) (2025-03-20) + +**Note:** Version bump only for package @wireapp/core + ## [46.20.3](https://github.com/wireapp/wire-web-packages/compare/@wireapp/core@46.20.2...@wireapp/core@46.20.3) (2025-03-20) **Note:** Version bump only for package @wireapp/core diff --git a/packages/core/package.json b/packages/core/package.json index f8d584ab71..ab975334a1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -61,6 +61,6 @@ "test:coverage": "jest --coverage", "watch": "tsc --watch" }, - "version": "46.20.3", + "version": "46.20.8", "gitHead": "5339f01fe01ef0871da8c8bc8662fbe9e604754a" } diff --git a/packages/core/src/conversation/content/ContentType.ts b/packages/core/src/conversation/content/ContentType.guards.ts similarity index 95% rename from packages/core/src/conversation/content/ContentType.ts rename to packages/core/src/conversation/content/ContentType.guards.ts index 26ed00f196..d2a035d857 100644 --- a/packages/core/src/conversation/content/ContentType.ts +++ b/packages/core/src/conversation/content/ContentType.guards.ts @@ -36,6 +36,7 @@ import { ImageAssetContent, ImageContent, LocationContent, + MultiPartContent, ReactionContent, TextContent, } from '.'; @@ -111,3 +112,7 @@ export function isReactionContent(content: ConversationContent): content is Reac export function isTextContent(content: ConversationContent): content is TextContent { return !!(content as TextContent).text; } + +export function isMultiPartContent(content: ConversationContent): content is MultiPartContent { + return !!(content as MultiPartContent).text && !!(content as MultiPartContent).attachments; +} diff --git a/packages/core/src/conversation/content/ConversationContent.ts b/packages/core/src/conversation/content/ConversationContent.ts index d9aae67446..b32e87ab1a 100644 --- a/packages/core/src/conversation/content/ConversationContent.ts +++ b/packages/core/src/conversation/content/ConversationContent.ts @@ -45,6 +45,7 @@ import { TextContent, InCallEmojiContent, InCallHandRaiseContent, + MultiPartContent, } from '.'; export type ConversationContent = @@ -73,4 +74,5 @@ export type ConversationContent = | ReactionContent | InCallEmojiContent | InCallHandRaiseContent - | TextContent; + | TextContent + | MultiPartContent; diff --git a/packages/core/src/conversation/content/MultipartContent.ts b/packages/core/src/conversation/content/MultipartContent.ts new file mode 100644 index 0000000000..b6024581d7 --- /dev/null +++ b/packages/core/src/conversation/content/MultipartContent.ts @@ -0,0 +1,24 @@ +/* + * Wire + * Copyright (C) 2018 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {IMultipart, IAttachment, ICellAsset, IAsset} from '@pydio/protocol-messaging'; +export {IMultipart as MultiPartContent}; +export {IAttachment as MultiPartAttachment}; +export {ICellAsset as MultiPartCellAsset}; +export {IAsset as MultiPartAsset}; diff --git a/packages/core/src/conversation/content/QuoteContent.ts b/packages/core/src/conversation/content/QuoteContent.ts index 47550e403f..172ebcb072 100644 --- a/packages/core/src/conversation/content/QuoteContent.ts +++ b/packages/core/src/conversation/content/QuoteContent.ts @@ -19,11 +19,11 @@ import {IQuote} from '@pydio/protocol-messaging'; -import {AssetContent, LocationContent, TextContent} from '.'; +import {AssetContent, LocationContent, MultiPartContent, TextContent} from '.'; export {IQuote as QuoteContent}; export interface QuoteMessageContent { - content: AssetContent | LocationContent | TextContent; + content: AssetContent | LocationContent | TextContent | MultiPartContent; quotedMessageId: string; } diff --git a/packages/core/src/conversation/content/index.ts b/packages/core/src/conversation/content/index.ts index 1463d3961e..1930ebdf74 100644 --- a/packages/core/src/conversation/content/index.ts +++ b/packages/core/src/conversation/content/index.ts @@ -20,7 +20,7 @@ export {LegalHoldStatus} from '@pydio/protocol-messaging'; export {Connection as ConnectionContent} from '@wireapp/api-client/lib/connection/'; -import * as ContentType from './ContentType'; +import * as ContentType from './ContentType.guards'; export {ContentType}; export * from './AssetContent'; @@ -49,3 +49,4 @@ export * from './TextContent'; export * from './TweetContent'; export * from './InCallEmojiContent'; export * from './InCallHandRaiseContent'; +export * from './MultipartContent'; diff --git a/packages/core/src/conversation/message/MessageBuilder.ts b/packages/core/src/conversation/message/MessageBuilder.ts index 886e61fc7b..c60208c0d0 100644 --- a/packages/core/src/conversation/message/MessageBuilder.ts +++ b/packages/core/src/conversation/message/MessageBuilder.ts @@ -64,6 +64,7 @@ import { } from './OtrMessage'; import {AssetTransferState} from '../AssetTransferState'; +import {MultiPartContent, TextContent} from '../content'; import {GenericMessageType} from '../GenericMessageType'; import {MessageToProtoMapper} from '../message/MessageToProtoMapper'; @@ -368,6 +369,20 @@ export function buildHideMessage(payload: HideMessage['content']): GenericMessag }); } +export function buildMultipartMessage( + attachments: MultiPartContent['attachments'], + textContent: TextContent, + messageId: string = createId(), +): GenericMessage { + return GenericMessage.create({ + [GenericMessageType.MULTIPART]: { + attachments, + text: MessageToProtoMapper.mapText(textContent), + }, + messageId: messageId, + }); +} + export function buildTextMessage( payloadBundle: TextMessage['content'], messageId: string = createId(), diff --git a/packages/core/src/conversation/message/OtrMessage.ts b/packages/core/src/conversation/message/OtrMessage.ts index f58b8246c8..9ef6f69bbd 100644 --- a/packages/core/src/conversation/message/OtrMessage.ts +++ b/packages/core/src/conversation/message/OtrMessage.ts @@ -41,8 +41,14 @@ import { TextContent, InCallEmojiContent, InCallHandRaiseContent, + MultiPartContent, } from '../content'; +export interface MultiPartMessage extends BasePayloadBundle { + content: MultiPartContent; + type: PayloadBundleType.MULTIPART; +} + export interface TextMessage extends BasePayloadBundle { content: TextContent; type: PayloadBundleType.TEXT; @@ -168,6 +174,7 @@ export type OtrMessage = | PingMessage | ReactionMessage | ResetSessionMessage - | TextMessage; + | TextMessage + | MultiPartMessage; -export type QuotableMessage = EditedTextMessage | ImageAssetMessage | LocationMessage | TextMessage; +export type QuotableMessage = EditedTextMessage | ImageAssetMessage | LocationMessage | TextMessage | MultiPartMessage; diff --git a/packages/core/src/conversation/message/PayloadBundle.ts b/packages/core/src/conversation/message/PayloadBundle.ts index 9ec85c129b..6899ee4fde 100644 --- a/packages/core/src/conversation/message/PayloadBundle.ts +++ b/packages/core/src/conversation/message/PayloadBundle.ts @@ -91,4 +91,5 @@ export enum PayloadBundleType { USER_LEGAL_HOLD_REQUEST = 'PayloadBundleType.USER_LEGAL_HOLD_REQUEST', USER_PROPERTIES_SET = 'PayloadBundleType.USER_PROPERTIES_SET', USER_UPDATE = 'PayloadBundleType.USER_UPDATE', + MULTIPART = 'PayloadBundleType.MULTIPART', } diff --git a/packages/core/src/cryptography/GenericMessageMapper.ts b/packages/core/src/cryptography/GenericMessageMapper.ts index 5d808da4e1..444bf921b3 100644 --- a/packages/core/src/cryptography/GenericMessageMapper.ts +++ b/packages/core/src/cryptography/GenericMessageMapper.ts @@ -31,6 +31,7 @@ import { HiddenContent, KnockContent, LocationContent, + MultiPartContent, ReactionContent, TextContent, } from '../conversation/content'; @@ -255,6 +256,17 @@ export class GenericMessageMapper { type: PayloadBundleType.REACTION, }; } + case GenericMessageType.MULTIPART: { + const {text, attachments, legalHoldStatus} = genericMessage[GenericMessageType.MULTIPART]; + + const content: MultiPartContent = {attachments, legalHoldStatus, text}; + + return { + ...baseMessage, + content, + type: PayloadBundleType.MULTIPART, + }; + } default: { this.logger.warn(`Unhandled event type "${genericMessage.content}": ${genericMessage}`); return {