diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 099a45938b03d..dddedc3bfd358 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -8,6 +8,27 @@ import { isChatGetThreadsListProps, isChatDeleteProps, isChatSyncMessagesProps, + isChatGetMessageProps, + isChatPinMessageProps, + isChatPostMessageProps, + isChatSearchProps, + isChatSendMessageProps, + isChatStarMessageProps, + isChatUnpinMessageProps, + isChatUnstarMessageProps, + isChatIgnoreUserProps, + isChatGetPinnedMessagesProps, + isChatFollowMessageProps, + isChatUnfollowMessageProps, + isChatGetMentionedMessagesProps, + isChatOTRProps, + isChatReactProps, + isChatGetDeletedMessagesProps, + isChatSyncThreadsListProps, + isChatGetThreadMessagesProps, + isChatSyncThreadMessagesProps, + isChatGetStarredMessagesProps, + isChatGetDiscussionsProps, } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; @@ -123,13 +144,10 @@ API.v1.addRoute( 'chat.getMessage', { authRequired: true, + validateParams: isChatGetMessageProps, }, { async get() { - if (!this.queryParams.msgId) { - return API.v1.failure('The "msgId" query parameter must be provided.'); - } - const msg = await Meteor.callAsync('getSingleMessage', this.queryParams.msgId); if (!msg) { @@ -147,13 +165,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.pinMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatPinMessageProps }, { async post() { - if (!this.bodyParams.messageId?.trim()) { - throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is missing.'); - } - const msg = await Messages.findOneById(this.bodyParams.messageId); if (!msg) { @@ -173,7 +187,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.postMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatPostMessageProps }, { async post() { const { text, attachments } = this.bodyParams; @@ -210,7 +224,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.search', - { authRequired: true }, + { authRequired: true, validateParams: isChatSearchProps }, { async get() { const { roomId, searchText } = this.queryParams; @@ -245,13 +259,9 @@ API.v1.addRoute( // one channel whereas the other one allows for sending to more than one channel at a time. API.v1.addRoute( 'chat.sendMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatSendMessageProps }, { async post() { - if (!this.bodyParams.message) { - throw new Meteor.Error('error-invalid-params', 'The "message" parameter must be provided.'); - } - if (MessageTypes.isSystemMessage(this.bodyParams.message)) { throw new Error("Cannot send system messages using 'chat.sendMessage'"); } @@ -270,13 +280,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.starMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatStarMessageProps }, { async post() { - if (!this.bodyParams.messageId?.trim()) { - throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is required.'); - } - const msg = await Messages.findOneById(this.bodyParams.messageId); if (!msg) { @@ -296,13 +302,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.unPinMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatUnpinMessageProps }, { async post() { - if (!this.bodyParams.messageId?.trim()) { - throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is required.'); - } - const msg = await Messages.findOneById(this.bodyParams.messageId); if (!msg) { @@ -318,13 +320,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.unStarMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatUnstarMessageProps }, { async post() { - if (!this.bodyParams.messageId?.trim()) { - throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is required.'); - } - const msg = await Messages.findOneById(this.bodyParams.messageId); if (!msg) { @@ -386,13 +384,9 @@ API.v1.addRoute( API.v1.addRoute( 'chat.react', - { authRequired: true }, + { authRequired: true, validateParams: isChatReactProps }, { async post() { - if (!this.bodyParams.messageId?.trim()) { - throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is missing.'); - } - const msg = await Messages.findOneById(this.bodyParams.messageId); if (!msg) { @@ -435,7 +429,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.ignoreUser', - { authRequired: true }, + { authRequired: true, validateParams: isChatIgnoreUserProps }, { async get() { const { rid, userId } = this.queryParams; @@ -443,14 +437,6 @@ API.v1.addRoute( ignore = typeof ignore === 'string' ? /true|1/.test(ignore) : ignore; - if (!rid?.trim()) { - throw new Meteor.Error('error-room-id-param-not-provided', 'The required "rid" param is missing.'); - } - - if (!userId?.trim()) { - throw new Meteor.Error('error-user-id-param-not-provided', 'The required "userId" param is missing.'); - } - await Meteor.callAsync('ignoreUser', { rid, userId, ignore }); return API.v1.success(); @@ -460,23 +446,13 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getDeletedMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetDeletedMessagesProps }, { async get() { const { roomId, since } = this.queryParams; const { offset, count } = await getPaginationItems(this.queryParams); - if (!roomId) { - throw new Meteor.Error('The required "roomId" query param is missing.'); - } - - if (!since) { - throw new Meteor.Error('The required "since" query param is missing.'); - } else if (isNaN(Date.parse(since))) { - throw new Meteor.Error('The "since" query parameter must be a valid date.'); - } - - const { cursor, totalCount } = await Messages.trashFindPaginatedDeletedAfter( + const { cursor, totalCount } = Messages.trashFindPaginatedDeletedAfter( new Date(since), { rid: roomId }, { @@ -500,21 +476,17 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getPinnedMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetPinnedMessagesProps }, { async get() { const { roomId } = this.queryParams; const { offset, count } = await getPaginationItems(this.queryParams); - if (!roomId) { - throw new Meteor.Error('error-roomId-param-not-provided', 'The required "roomId" query param is missing.'); - } - if (!(await canAccessRoomIdAsync(roomId, this.userId))) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - const { cursor, totalCount } = await Messages.findPaginatedPinnedByRoom(roomId, { + const { cursor, totalCount } = Messages.findPaginatedPinnedByRoom(roomId, { skip: offset, limit: count, }); @@ -580,7 +552,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.syncThreadsList', - { authRequired: true }, + { authRequired: true, validateParams: isChatSyncThreadsListProps }, { async get() { const { rid } = this.queryParams; @@ -590,12 +562,7 @@ API.v1.addRoute( if (!settings.get('Threads_enabled')) { throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); } - if (!rid) { - throw new Meteor.Error('error-room-id-param-not-provided', 'The required "rid" query param is missing.'); - } - if (!updatedSince) { - throw new Meteor.Error('error-updatedSince-param-invalid', 'The required param "updatedSince" is missing.'); - } + if (isNaN(Date.parse(updatedSince))) { throw new Meteor.Error('error-updatedSince-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); } else { @@ -629,7 +596,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getThreadMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetThreadMessagesProps }, { async get() { const { tmid } = this.queryParams; @@ -639,9 +606,7 @@ API.v1.addRoute( if (!settings.get('Threads_enabled')) { throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); } - if (!tmid) { - throw new Meteor.Error('error-invalid-params', 'The required "tmid" query param is missing.'); - } + const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } }); if (!thread?.rid) { throw new Meteor.Error('error-invalid-message', 'Invalid Message'); @@ -652,7 +617,7 @@ API.v1.addRoute( if (!room || !user || !(await canAccessRoomAsync(room, user))) { throw new Meteor.Error('error-not-allowed', 'Not Allowed'); } - const { cursor, totalCount } = await Messages.findPaginated( + const { cursor, totalCount } = Messages.findPaginated( { ...query, tmid }, { sort: sort || { ts: 1 }, @@ -676,7 +641,7 @@ API.v1.addRoute( API.v1.addRoute( 'chat.syncThreadMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatSyncThreadMessagesProps }, { async get() { const { tmid } = this.queryParams; @@ -686,12 +651,7 @@ API.v1.addRoute( if (!settings.get('Threads_enabled')) { throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); } - if (!tmid) { - throw new Meteor.Error('error-invalid-params', 'The required "tmid" query param is missing.'); - } - if (!updatedSince) { - throw new Meteor.Error('error-updatedSince-param-invalid', 'The required param "updatedSince" is missing.'); - } + if (isNaN(Date.parse(updatedSince))) { throw new Meteor.Error('error-updatedSince-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); } else { @@ -701,6 +661,7 @@ API.v1.addRoute( if (!thread?.rid) { throw new Meteor.Error('error-invalid-message', 'Invalid Message'); } + // TODO: promise.all? this.user? const user = await Users.findOneById(this.userId, { projection: { _id: 1 } }); const room = await Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } }); @@ -719,15 +680,11 @@ API.v1.addRoute( API.v1.addRoute( 'chat.followMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatFollowMessageProps }, { async post() { const { mid } = this.bodyParams; - if (!mid) { - throw new Meteor.Error('The required "mid" body param is missing.'); - } - await Meteor.callAsync('followMessage', { mid }); return API.v1.success(); @@ -737,15 +694,11 @@ API.v1.addRoute( API.v1.addRoute( 'chat.unfollowMessage', - { authRequired: true }, + { authRequired: true, validateParams: isChatUnfollowMessageProps }, { async post() { const { mid } = this.bodyParams; - if (!mid) { - throw new Meteor.Error('The required "mid" body param is missing.'); - } - await Meteor.callAsync('unfollowMessage', { mid }); return API.v1.success(); @@ -755,15 +708,13 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getMentionedMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetMentionedMessagesProps }, { async get() { const { roomId } = this.queryParams; const { sort } = await this.parseJsonQuery(); const { offset, count } = await getPaginationItems(this.queryParams); - if (!roomId) { - throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); - } + const messages = await findMentionedMessages({ uid: this.userId, roomId, @@ -781,16 +732,13 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getStarredMessages', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetStarredMessagesProps }, { async get() { const { roomId } = this.queryParams; const { sort } = await this.parseJsonQuery(); const { offset, count } = await getPaginationItems(this.queryParams); - if (!roomId) { - throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); - } const messages = await findStarredMessages({ uid: this.userId, roomId, @@ -810,16 +758,13 @@ API.v1.addRoute( API.v1.addRoute( 'chat.getDiscussions', - { authRequired: true }, + { authRequired: true, validateParams: isChatGetDiscussionsProps }, { async get() { const { roomId, text } = this.queryParams; const { sort } = await this.parseJsonQuery(); const { offset, count } = await getPaginationItems(this.queryParams); - if (!roomId) { - throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); - } const messages = await findDiscussionsFromRoom({ uid: this.userId, roomId, @@ -837,19 +782,11 @@ API.v1.addRoute( API.v1.addRoute( 'chat.otr', - { authRequired: true }, + { authRequired: true, validateParams: isChatOTRProps }, { async post() { const { roomId, type: otrType } = this.bodyParams; - if (!roomId) { - throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); - } - - if (!otrType) { - throw new Meteor.Error('error-invalid-params', 'The required "type" query param is missing.'); - } - const { username, type } = this.user; if (!username) { diff --git a/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts b/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts index da67d0601344a..5bcb23d76d342 100644 --- a/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts +++ b/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts @@ -47,7 +47,7 @@ test.describe.serial('Threads', () => { const messageLink = `/channel/${targetChannel.name}?msg=${mainMessage._id}`; await page.goto(messageLink); - const message = await page.locator(`[aria-label=\"Message list\"] [data-id=\"${mainMessage._id}\"]`); + const message = page.locator(`[aria-label=\"Message list\"] [data-id=\"${mainMessage._id}\"]`); await expect(message).toBeVisible(); await expect(message).toBeInViewport(); diff --git a/apps/meteor/tests/end-to-end/api/chat.ts b/apps/meteor/tests/end-to-end/api/chat.ts index 43df270bbec1e..2656799332a77 100644 --- a/apps/meteor/tests/end-to-end/api/chat.ts +++ b/apps/meteor/tests/end-to-end/api/chat.ts @@ -51,7 +51,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', '[invalid-channel]'); }) .end(done); }); @@ -1386,7 +1385,7 @@ describe('[Chat]', () => { .set(credentials) .send({ roomId: testChannel._id, - msg: 'Sample message', + text: 'Sample message', customFields, }) .expect('Content-Type', 'application/json') @@ -2455,7 +2454,7 @@ describe('[Chat]', () => { }); describe('when an error occurs', () => { - it('should return statusCode 400 and an error when "roomId" is not provided', (done) => { + it('should return statusCode 400', (done) => { void request .get(api('chat.getDeletedMessages')) .set(credentials) @@ -2468,7 +2467,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('The required "roomId" query param is missing.'); }) .end(done); }); @@ -2485,7 +2483,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('The required "since" query param is missing.'); }) .end(done); }); @@ -2503,7 +2500,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('The "since" query parameter must be a valid date.'); }) .end(done); }); @@ -2923,7 +2919,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-roomId-param-not-provided'); }) .end(done); }); @@ -2952,7 +2947,7 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-invalid-params'); + expect(res.body.errorType).to.be.equal('invalid-params'); }) .end(done); }); @@ -3011,7 +3006,7 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-invalid-params'); + expect(res.body.errorType).to.be.equal('invalid-params'); }) .end(done); }); @@ -3087,7 +3082,6 @@ describe('[Chat]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-invalid-params'); }) .end(done); }); @@ -3847,8 +3841,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-room-id-param-not-provided'); - expect(res.body).to.have.property('error', 'The required "rid" query param is missing. [error-room-id-param-not-provided]'); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -3866,8 +3859,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-updatedSince-param-invalid'); - expect(res.body).to.have.property('error', 'The required param "updatedSince" is missing. [error-updatedSince-param-invalid]'); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -3886,11 +3878,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-updatedSince-param-invalid'); - expect(res.body).to.have.property( - 'error', - 'The "updatedSince" query parameter must be a valid date. [error-updatedSince-param-invalid]', - ); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -4088,7 +4076,7 @@ describe('Threads', () => { .set(credentials) .query({ tmid: threadMessage.tmid, - updatedSince: 'updatedSince', + updatedSince: new Date().toISOString(), }) .expect('Content-Type', 'application/json') .expect(400) @@ -4111,8 +4099,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-invalid-params'); - expect(res.body).to.have.property('error', 'The required "tmid" query param is missing. [error-invalid-params]'); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -4130,8 +4117,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-updatedSince-param-invalid'); - expect(res.body).to.have.property('error', 'The required param "updatedSince" is missing. [error-updatedSince-param-invalid]'); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -4150,11 +4136,7 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-updatedSince-param-invalid'); - expect(res.body).to.have.property( - 'error', - 'The "updatedSince" query parameter must be a valid date. [error-updatedSince-param-invalid]', - ); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); @@ -4461,7 +4443,6 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('invalid-params'); }); }); it('should return statusCode 400 and an error when "url" is not provided', async () => { @@ -4475,7 +4456,6 @@ describe('Threads', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('invalid-params'); }); }); it('should return statusCode 400 and an error when "roomId" is provided but user is not in the room', async () => { diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index d2fb3915b405f..ebfda43e1a751 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -7,14 +7,10 @@ import type { MessageUrl, IThreadMainMessage, } from '@rocket.chat/core-typings'; -import Ajv from 'ajv'; +import { ajv } from './Ajv'; import type { PaginatedRequest } from '../helpers/PaginatedRequest'; -const ajv = new Ajv({ - coerceTypes: true, -}); - type ChatSendMessage = { message: Partial; previewUrls?: string[]; @@ -100,6 +96,7 @@ const chatFollowMessageSchema = { properties: { mid: { type: 'string', + minLength: 1, }, }, required: ['mid'], @@ -117,6 +114,7 @@ const chatUnfollowMessageSchema = { properties: { mid: { type: 'string', + minLength: 1, }, }, required: ['mid'], @@ -134,6 +132,7 @@ const ChatGetMessageSchema = { properties: { msgId: { type: 'string', + minLength: 1, }, }, required: ['msgId'], @@ -185,6 +184,7 @@ const ChatPinMessageSchema = { properties: { messageId: { type: 'string', + minLength: 1, }, }, required: ['messageId'], @@ -202,6 +202,7 @@ const ChatUnpinMessageSchema = { properties: { messageId: { type: 'string', + minLength: 1, }, }, required: ['messageId'], @@ -220,18 +221,16 @@ const ChatGetDiscussionsSchema = { properties: { roomId: { type: 'string', + minLength: 1, }, text: { type: 'string', - nullable: true, }, offset: { type: 'number', - nullable: true, }, count: { type: 'number', - nullable: true, }, }, required: ['roomId'], @@ -320,9 +319,11 @@ const ChatSyncThreadsListSchema = { properties: { rid: { type: 'string', + minLength: 1, }, updatedSince: { type: 'string', + format: 'iso-date-time', }, }, required: ['rid', 'updatedSince'], @@ -371,6 +372,7 @@ const ChatReactSchema = { }, messageId: { type: 'string', + minLength: 1, }, shouldReact: { type: 'boolean', @@ -388,6 +390,7 @@ const ChatReactSchema = { }, messageId: { type: 'string', + minLength: 1, }, shouldReact: { type: 'boolean', @@ -417,12 +420,15 @@ const ChatIgnoreUserSchema = { properties: { rid: { type: 'string', + minLength: 1, }, userId: { type: 'string', + minLength: 1, }, ignore: { type: 'string', + minLength: 1, }, }, required: ['rid', 'userId', 'ignore'], @@ -527,6 +533,7 @@ const GetStarredMessagesSchema = { properties: { roomId: { type: 'string', + minLength: 1, }, count: { type: 'number', @@ -559,6 +566,7 @@ const GetPinnedMessagesSchema = { properties: { roomId: { type: 'string', + minLength: 1, }, count: { type: 'number', @@ -591,6 +599,7 @@ const GetMentionedMessagesSchema = { properties: { roomId: { type: 'string', + minLength: 1, }, count: { type: 'number', @@ -664,9 +673,11 @@ const ChatSyncThreadMessagesSchema = { properties: { tmid: { type: 'string', + minLength: 1, }, updatedSince: { type: 'string', + format: 'iso-date-time', }, count: { type: 'number', @@ -696,6 +707,7 @@ const ChatGetThreadMessagesSchema = { properties: { tmid: { type: 'string', + minLength: 1, }, count: { type: 'number', @@ -726,9 +738,12 @@ const ChatGetDeletedMessagesSchema = { properties: { roomId: { type: 'string', + minLength: 1, }, since: { type: 'string', + minLength: 1, + format: 'iso-date-time', }, count: { type: 'number', @@ -808,6 +823,9 @@ const ChatPostMessageSchema = { }, nullable: true, }, + tmid: { + type: 'string', + }, customFields: { type: 'object', nullable: true, @@ -887,6 +905,24 @@ const ChatGetURLPreviewSchema = { export const isChatGetURLPreviewProps = ajv.compile(ChatGetURLPreviewSchema); +type ChatOTR = { roomId: string; type: OtrSystemMessages }; +const ChatOTRSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + minLength: 1, + }, + type: { + type: 'string', + enum: ['user_joined_otr', 'user_requested_otr_key_refresh', 'user_key_refreshed_successfully'], + }, + }, + required: ['roomId', 'type'], + additionalProperties: false, +}; +export const isChatOTRProps = ajv.compile(ChatOTRSchema); + export type ChatEndpoints = { '/v1/chat.sendMessage': { POST: (params: ChatSendMessage) => { @@ -1035,7 +1071,7 @@ export type ChatEndpoints = { }; }; '/v1/chat.otr': { - POST: (params: { roomId: string; type: OtrSystemMessages }) => void; + POST: (params: ChatOTR) => void; }; '/v1/chat.getURLPreview': { GET: (params: ChatGetURLPreview) => { urlPreview: MessageUrl };