diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 5a6707beb6a10..a2b5f93d1b0e3 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -434,7 +434,7 @@ API.v1.addRoute( } const sent = await applyAirGappedRestrictionsValidation(() => - executeSendMessage(this.userId, this.bodyParams.message as Pick, this.bodyParams.previewUrls), + executeSendMessage(this.userId, this.bodyParams.message as Pick, { previewUrls: this.bodyParams.previewUrls }), ); const [message] = await normalizeMessagesForUser([sent], this.userId); diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index 66758bc7faa1c..db7a017ee7a01 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -19,7 +19,21 @@ import { settings } from '../../../settings/server'; import { sendMessage } from '../functions/sendMessage'; import { RateLimiter } from '../lib'; -export async function executeSendMessage(uid: IUser['_id'], message: AtLeast, previewUrls?: string[]) { +/** + * + * @param uid + * @param message + * @param extraInfo + * - ts: The timestamp of the message. the message object already has a ts, but this value is validated and only a window of 10 seconds is allowed to be used. this value overrides the message.ts value without validation. + * + * + * @returns + */ +export async function executeSendMessage( + uid: IUser['_id'], + message: AtLeast, + extraInfo?: { ts?: Date; previewUrls?: string[] }, +) { if (message.tshow && !message.tmid) { throw new Meteor.Error('invalid-params', 'tshow provided but missing tmid', { method: 'sendMessage', @@ -32,7 +46,10 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast 60000) { throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', { @@ -40,11 +57,10 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast 10000) { - message.ts = new Date(); } - } else { - message.ts = new Date(); + if (tsDiff > 10000) { + message.ts = now; + } } if (message.msg) { @@ -90,7 +106,7 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast({ } try { - return await applyAirGappedRestrictionsValidation(() => executeSendMessage(uid, message, previewUrls)); + return await applyAirGappedRestrictionsValidation(() => executeSendMessage(uid, message, { previewUrls })); } catch (error: any) { if (['error-not-allowed', 'restricted-workspace'].includes(error.error || error.message)) { throw new Meteor.Error(error.error || error.message, error.reason, { diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 9b7e59fb800d5..669441a63c186 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -95,6 +95,7 @@ export class MessageService extends ServiceClassInternal implements IMessageServ files, attachments, thread, + ts, }: { fromId: string; rid: string; @@ -108,23 +109,28 @@ export class MessageService extends ServiceClassInternal implements IMessageServ files?: IMessage['files']; attachments?: IMessage['attachments']; thread?: { tmid: string; tshow: boolean }; + ts: Date; }): Promise { - return executeSendMessage(fromId, { - rid, - msg, - ...thread, - federation: { - eventId: federation_event_id, - version: 1, + return executeSendMessage( + fromId, + { + rid, + msg, + ...thread, + federation: { + eventId: federation_event_id, + version: 1, + }, + ...(file && { file }), + ...(files && { files }), + ...(attachments && { attachments }), + ...(e2e_content && { + t: 'e2e', + content: e2e_content, + }), }, - ...(file && { file }), - ...(files && { files }), - ...(attachments && { attachments }), - ...(e2e_content && { - t: 'e2e', - content: e2e_content, - }), - }); + { ts }, + ); } async sendMessageWithValidation(user: IUser, message: Partial, room: Partial, upsert = false): Promise { diff --git a/ee/packages/federation-matrix/src/events/message.ts b/ee/packages/federation-matrix/src/events/message.ts index 609d33459f530..67ef247332625 100644 --- a/ee/packages/federation-matrix/src/events/message.ts +++ b/ee/packages/federation-matrix/src/events/message.ts @@ -225,6 +225,7 @@ export function message() { msg: formatted, federation_event_id: eventId, thread, + ts: new Date(event.origin_server_ts), }); return; } @@ -242,7 +243,7 @@ export function message() { eventId, thread, ); - await Message.saveMessageFromFederation(result); + await Message.saveMessageFromFederation({ ...result, ts: new Date(event.origin_server_ts) }); } else { const formatted = toInternalMessageFormat({ rawMessage: messageBody, @@ -257,6 +258,7 @@ export function message() { msg: formatted, federation_event_id: eventId, thread, + ts: new Date(event.origin_server_ts), }); } } catch (error) { @@ -359,6 +361,7 @@ export function message() { }, federation_event_id: eventId, thread, + ts: new Date(event.origin_server_ts), }); return; } @@ -372,6 +375,7 @@ export function message() { }, federation_event_id: eventId, thread, + ts: new Date(event.origin_server_ts), }); } catch (error) { logger.error(error, 'Error processing Matrix message:'); diff --git a/packages/core-services/src/types/IMessageService.ts b/packages/core-services/src/types/IMessageService.ts index 5532a553f6d6f..c9490e81bb01e 100644 --- a/packages/core-services/src/types/IMessageService.ts +++ b/packages/core-services/src/types/IMessageService.ts @@ -19,6 +19,7 @@ export interface IMessageService { files, attachments, thread, + ts, }: { fromId: string; rid: string; @@ -32,6 +33,7 @@ export interface IMessageService { files?: IMessage['files']; attachments?: IMessage['attachments']; thread?: { tmid: string; tshow: boolean }; + ts: Date; }): Promise; saveSystemMessageAndNotifyUser( type: MessageTypesValues,