From e34e05bf77cbf972b71c65e5d41d972b6bedeb09 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 2 May 2023 11:11:18 -0600 Subject: [PATCH] refactor: Convert `cors` and `invites` to ts (#29076) --- .../app/cors/client/{index.js => index.ts} | 0 .../app/cors/server/{cors.js => cors.ts} | 44 +++++++++++++------ .../app/cors/server/{index.js => index.ts} | 2 +- ...rHandler.js => RocketChat.ErrorHandler.ts} | 22 +++++++--- .../app/file-upload/server/lib/FileUpload.ts | 1 - .../client/{helper.js => helper.ts} | 12 ++--- ...on.js => mountQueriesBasedOnPermission.ts} | 12 ++--- .../server/{logger.js => logger.ts} | 0 ...rCreateInvite.js => findOrCreateInvite.ts} | 12 ++--- .../{listInvites.js => listInvites.ts} | 2 +- .../{removeInvite.js => removeInvite.ts} | 3 +- .../{useInviteToken.js => useInviteToken.ts} | 10 ++++- ...eInviteToken.js => validateInviteToken.ts} | 4 +- .../server/{functions.js => functions.ts} | 24 ++++++---- packages/core-services/src/Events.ts | 2 +- 15 files changed, 96 insertions(+), 54 deletions(-) rename apps/meteor/app/cors/client/{index.js => index.ts} (100%) rename apps/meteor/app/cors/server/{cors.js => cors.ts} (66%) rename apps/meteor/app/cors/server/{index.js => index.ts} (80%) rename apps/meteor/app/error-handler/server/lib/{RocketChat.ErrorHandler.js => RocketChat.ErrorHandler.ts} (76%) rename apps/meteor/app/highlight-words/client/{helper.js => helper.ts} (66%) rename apps/meteor/app/integrations/server/lib/{mountQueriesBasedOnPermission.js => mountQueriesBasedOnPermission.ts} (84%) rename apps/meteor/app/integrations/server/{logger.js => logger.ts} (100%) rename apps/meteor/app/invites/server/functions/{findOrCreateInvite.js => findOrCreateInvite.ts} (88%) rename apps/meteor/app/invites/server/functions/{listInvites.js => listInvites.ts} (89%) rename apps/meteor/app/invites/server/functions/{removeInvite.js => removeInvite.ts} (84%) rename apps/meteor/app/invites/server/functions/{useInviteToken.js => useInviteToken.ts} (85%) rename apps/meteor/app/invites/server/functions/{validateInviteToken.js => validateInviteToken.ts} (88%) rename apps/meteor/app/threads/server/{functions.js => functions.ts} (63%) diff --git a/apps/meteor/app/cors/client/index.js b/apps/meteor/app/cors/client/index.ts similarity index 100% rename from apps/meteor/app/cors/client/index.js rename to apps/meteor/app/cors/client/index.ts diff --git a/apps/meteor/app/cors/server/cors.js b/apps/meteor/app/cors/server/cors.ts similarity index 66% rename from apps/meteor/app/cors/server/cors.js rename to apps/meteor/app/cors/server/cors.ts index db0e4dc4fc95..534a65aa6d95 100644 --- a/apps/meteor/app/cors/server/cors.js +++ b/apps/meteor/app/cors/server/cors.ts @@ -1,22 +1,27 @@ import url from 'url'; +import type http from 'http'; import { Meteor } from 'meteor/meteor'; +import type { StaticFiles } from 'meteor/webapp'; import { WebApp, WebAppInternals } from 'meteor/webapp'; import _ from 'underscore'; import { settings } from '../../settings/server'; import { Logger } from '../../logger/server'; +// Taken from 'connect' types +type NextFunction = (err?: any) => void; + const logger = new Logger('CORS'); -settings.watch( +settings.watch( 'Enable_CSP', Meteor.bindEnvironment((enabled) => { WebAppInternals.setInlineScriptsAllowed(!enabled); }), ); -WebApp.rawConnectHandlers.use(function (req, res, next) { +WebApp.rawConnectHandlers.use(function (_req: http.IncomingMessage, res: http.ServerResponse, next: NextFunction) { // XSS Protection for old browsers (IE) res.setHeader('X-XSS-Protection', '1'); @@ -24,11 +29,15 @@ WebApp.rawConnectHandlers.use(function (req, res, next) { res.setHeader('X-Content-Type-Options', 'nosniff'); if (settings.get('Iframe_Restrict_Access')) { - res.setHeader('X-Frame-Options', settings.get('Iframe_X_Frame_Options')); + res.setHeader('X-Frame-Options', settings.get('Iframe_X_Frame_Options')); } - if (settings.get('Enable_CSP')) { - const cdn_prefixes = [settings.get('CDN_PREFIX'), settings.get('CDN_PREFIX_ALL') ? null : settings.get('CDN_JSCSS_PREFIX')] + if (settings.get('Enable_CSP')) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const cdn_prefixes = [ + settings.get('CDN_PREFIX'), + settings.get('CDN_PREFIX_ALL') ? null : settings.get('CDN_JSCSS_PREFIX'), + ] .filter(Boolean) .join(' '); @@ -39,11 +48,11 @@ WebApp.rawConnectHandlers.use(function (req, res, next) { .filter(Boolean) .join(' '); const external = [ - settings.get('Accounts_OAuth_Apple') && 'https://appleid.cdn-apple.com', - settings.get('PiwikAnalytics_enabled') && settings.get('PiwikAnalytics_url'), - settings.get('GoogleAnalytics_enabled' && 'https://www.google-analytics.com'), + settings.get('Accounts_OAuth_Apple') && 'https://appleid.cdn-apple.com', + settings.get('PiwikAnalytics_enabled') && settings.get('PiwikAnalytics_url'), + settings.get('GoogleAnalytics_enabled') && 'https://www.google-analytics.com', ...settings - .get('Extra_CSP_Domains') + .get('Extra_CSP_Domains') .split(/[ \n\,]/gim) .filter((e) => Boolean(e.trim())), ] @@ -69,7 +78,13 @@ WebApp.rawConnectHandlers.use(function (req, res, next) { const _staticFilesMiddleware = WebAppInternals.staticFilesMiddleware; -WebAppInternals._staticFilesMiddleware = function (staticFiles, req, res, next) { +// @ts-expect-error - accessing internal property of webapp +WebAppInternals._staticFilesMiddleware = function ( + staticFiles: StaticFiles, + req: http.IncomingMessage, + res: http.ServerResponse, + next: NextFunction, +) { res.setHeader('Access-Control-Allow-Origin', '*'); return _staticFilesMiddleware(staticFiles, req, res, next); }; @@ -90,15 +105,16 @@ WebApp.httpServer.addListener('request', function (req, res, ...args) { return; } - const remoteAddress = req.connection.remoteAddress || req.socket.remoteAddress; + const remoteAddress = req.connection.remoteAddress || req.socket.remoteAddress || ''; const localhostRegexp = /^\s*(127\.0\.0\.1|::1)\s*$/; - const localhostTest = function (x) { + const localhostTest = function (x: string) { return localhostRegexp.test(x); }; const isLocal = localhostRegexp.test(remoteAddress) && - (!req.headers['x-forwarded-for'] || _.all(req.headers['x-forwarded-for'].split(','), localhostTest)); + (!req.headers['x-forwarded-for'] || _.all((req.headers['x-forwarded-for'] as string).split(','), localhostTest)); + // @ts-expect-error - `pair` is valid, but doesnt exists on types const isSsl = req.connection.pair || (req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'].indexOf('https') !== -1); logger.debug('req.url', req.url); @@ -108,7 +124,7 @@ WebApp.httpServer.addListener('request', function (req, res, ...args) { logger.debug('req.headers', req.headers); if (!isLocal && !isSsl) { - let host = req.headers.host || url.parse(Meteor.absoluteUrl()).hostname; + let host = req.headers.host || url.parse(Meteor.absoluteUrl()).hostname || ''; host = host.replace(/:\d+$/, ''); res.writeHead(302, { Location: `https://${host}${req.url}`, diff --git a/apps/meteor/app/cors/server/index.js b/apps/meteor/app/cors/server/index.ts similarity index 80% rename from apps/meteor/app/cors/server/index.js rename to apps/meteor/app/cors/server/index.ts index f327fce9aa82..f8805e23bc36 100644 --- a/apps/meteor/app/cors/server/index.js +++ b/apps/meteor/app/cors/server/index.ts @@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../settings/server'; Meteor.startup(function () { - settings.watch('Force_SSL', (value) => { + settings.watch('Force_SSL', (value) => { Meteor.absoluteUrl.defaultOptions.secure = Boolean(value); }); }); diff --git a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.js b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts similarity index 76% rename from apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.js rename to apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts index 213d51c991c4..9a4fc28b8f3e 100644 --- a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.js +++ b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts @@ -5,6 +5,12 @@ import { settings } from '../../../settings/server'; import { sendMessage } from '../../../lib/server'; class ErrorHandler { + reporting: boolean; + + rid: string | null; + + lastError: string | null; + constructor() { this.reporting = false; this.rid = null; @@ -13,11 +19,14 @@ class ErrorHandler { Meteor.startup(async () => { await this.registerHandlers(); - settings.watch('Log_Exceptions_to_Channel', async (value) => { + settings.watch('Log_Exceptions_to_Channel', async (value) => { this.rid = null; const roomName = value.trim(); if (roomName) { - this.rid = await this.getRoomId(roomName); + const rid = await this.getRoomId(roomName); + if (rid) { + this.rid = rid; + } } if (this.rid) { @@ -38,19 +47,20 @@ class ErrorHandler { await this.trackError(error.message, error.stack); }); + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const originalMeteorDebug = Meteor._debug; Meteor._debug = function (message, stack, ...args) { if (!self.reporting) { return originalMeteorDebug.call(this, message, stack); } - self.trackError(message, stack); + void self.trackError(message, stack); return originalMeteorDebug.apply(this, [message, stack, ...args]); }; } - async getRoomId(roomName) { - roomName = roomName.replace('#'); + async getRoomId(roomName: string): Promise { + roomName = roomName.replace('#', ''); const room = await Rooms.findOneByName(roomName, { projection: { _id: 1, t: 1 } }); if (!room || (room.t !== 'c' && room.t !== 'p')) { return; @@ -58,7 +68,7 @@ class ErrorHandler { return room._id; } - async trackError(message, stack) { + async trackError(message: string, stack?: string): Promise { if (!this.reporting || !this.rid || this.lastError === message) { return; } diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index 26ee6c190853..591c680696aa 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -683,7 +683,6 @@ export class FileUploadClass { async deleteById(fileId: string) { const file = await this.model.findOneById(fileId); - // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!file) { return; } diff --git a/apps/meteor/app/highlight-words/client/helper.js b/apps/meteor/app/highlight-words/client/helper.ts similarity index 66% rename from apps/meteor/app/highlight-words/client/helper.js rename to apps/meteor/app/highlight-words/client/helper.ts index 8749ce29ee61..2e61e42e416e 100644 --- a/apps/meteor/app/highlight-words/client/helper.js +++ b/apps/meteor/app/highlight-words/client/helper.ts @@ -1,8 +1,8 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; -const checkHighlightedWordsInUrls = (msg, urlRegex) => msg.match(urlRegex); +const checkHighlightedWordsInUrls = (msg: string, urlRegex: RegExp) => msg.match(urlRegex); -const removeHighlightedUrls = (msg, highlight, urlMatches) => { +const removeHighlightedUrls = (msg: string, highlight: string, urlMatches: string[]) => { const highlightRegex = new RegExp(highlight, 'gmi'); return urlMatches.reduce((msg, match) => { @@ -14,13 +14,13 @@ const removeHighlightedUrls = (msg, highlight, urlMatches) => { const highlightTemplate = '$1$2$3'; -export const getRegexHighlight = (highlight) => +export const getRegexHighlight = (highlight: string) => new RegExp( `(^|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(${escapeRegExp(highlight)})($|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(?![^<]*>|[^<>]*<\\/)`, 'gmi', ); -export const getRegexHighlightUrl = (highlight) => +export const getRegexHighlightUrl = (highlight: string) => new RegExp( `https?:\/\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)(${escapeRegExp( highlight, @@ -28,8 +28,8 @@ export const getRegexHighlightUrl = (highlight) => 'gmi', ); -export const highlightWords = (msg, highlights) => - highlights.reduce((msg, { highlight, regex, urlRegex }) => { +export const highlightWords = (msg: string, highlights: { highlight: string; regex: RegExp; urlRegex: RegExp }[]) => + highlights.reduce((msg: string, { highlight, regex, urlRegex }: { highlight: string; regex: RegExp; urlRegex: RegExp }) => { const urlMatches = checkHighlightedWordsInUrls(msg, urlRegex); if (!urlMatches) { return msg.replace(regex, highlightTemplate); diff --git a/apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.js b/apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.ts similarity index 84% rename from apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.js rename to apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.ts index 0ea1fa78b23e..9f07262e781c 100644 --- a/apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.js +++ b/apps/meteor/app/integrations/server/lib/mountQueriesBasedOnPermission.ts @@ -1,17 +1,19 @@ import { Meteor } from 'meteor/meteor'; +import type { DeepWritable } from '@rocket.chat/core-typings'; +import type { Filter } from 'mongodb'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -export const mountIntegrationQueryBasedOnPermissions = async (userId) => { +export const mountIntegrationQueryBasedOnPermissions = async (userId: string) => { if (!userId) { - throw new Meteor.Error('You must provide the userId to the "mountIntegrationQueryBasedOnPermissions" fucntion.'); + throw new Meteor.Error('You must provide the userId to the "mountIntegrationQueryBasedOnPermissions" function.'); } const canViewAllOutgoingIntegrations = await hasPermissionAsync(userId, 'manage-outgoing-integrations'); const canViewAllIncomingIntegrations = await hasPermissionAsync(userId, 'manage-incoming-integrations'); const canViewOnlyOwnOutgoingIntegrations = await hasPermissionAsync(userId, 'manage-own-outgoing-integrations'); const canViewOnlyOwnIncomingIntegrations = await hasPermissionAsync(userId, 'manage-own-incoming-integrations'); - const query = {}; + const query: DeepWritable> = {}; if (canViewAllOutgoingIntegrations && canViewAllIncomingIntegrations) { return query; @@ -36,7 +38,7 @@ export const mountIntegrationQueryBasedOnPermissions = async (userId) => { return query; }; -export const mountIntegrationHistoryQueryBasedOnPermissions = async (userId, integrationId) => { +export const mountIntegrationHistoryQueryBasedOnPermissions = async (userId: string, integrationId: string) => { if (!userId) { throw new Meteor.Error('You must provide the userId to the "mountIntegrationHistoryQueryBasedOnPermissions" fucntion.'); } @@ -45,7 +47,7 @@ export const mountIntegrationHistoryQueryBasedOnPermissions = async (userId, int } const canViewOnlyOwnOutgoingIntegrations = await hasPermissionAsync(userId, 'manage-own-outgoing-integrations'); - const canViewAllOutgoingIntegrations = await (userId, 'manage-outgoing-integrations'); + const canViewAllOutgoingIntegrations = await hasPermissionAsync(userId, 'manage-outgoing-integrations'); if (!canViewAllOutgoingIntegrations && canViewOnlyOwnOutgoingIntegrations) { return { 'integration._id': integrationId, 'integration._createdBy._id': userId }; } diff --git a/apps/meteor/app/integrations/server/logger.js b/apps/meteor/app/integrations/server/logger.ts similarity index 100% rename from apps/meteor/app/integrations/server/logger.js rename to apps/meteor/app/integrations/server/logger.ts diff --git a/apps/meteor/app/invites/server/functions/findOrCreateInvite.js b/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts similarity index 88% rename from apps/meteor/app/invites/server/functions/findOrCreateInvite.js rename to apps/meteor/app/invites/server/functions/findOrCreateInvite.ts index 761a98a1bfdd..a1d3487f88ed 100644 --- a/apps/meteor/app/invites/server/functions/findOrCreateInvite.js +++ b/apps/meteor/app/invites/server/functions/findOrCreateInvite.ts @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Random } from '@rocket.chat/random'; import { Invites, Subscriptions, Rooms } from '@rocket.chat/models'; import { api } from '@rocket.chat/core-services'; +import type { IInvite } from '@rocket.chat/core-typings'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; @@ -9,10 +10,10 @@ import { getURL } from '../../../utils/server/getURL'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; -function getInviteUrl(invite) { +function getInviteUrl(invite: Omit) { const { _id } = invite; - const useDirectLink = settings.get('Accounts_Registration_InviteUrlType') === 'direct'; + const useDirectLink = settings.get('Accounts_Registration_InviteUrlType') === 'direct'; return getURL( `invite/${_id}`, @@ -21,14 +22,14 @@ function getInviteUrl(invite) { cloud: !useDirectLink, cloud_route: 'invite', }, - settings.get('DeepLink_Url'), + settings.get('DeepLink_Url'), ); } const possibleDays = [0, 1, 7, 15, 30]; const possibleUses = [0, 1, 5, 10, 25, 50, 100]; -export const findOrCreateInvite = async (userId, invite) => { +export const findOrCreateInvite = async (userId: string, invite: Pick) => { if (!userId || !invite) { return false; } @@ -97,7 +98,7 @@ export const findOrCreateInvite = async (userId, invite) => { expires.setDate(expires.getDate() + days); } - const createInvite = { + const createInvite: Omit = { _id, days, maxUses, @@ -105,6 +106,7 @@ export const findOrCreateInvite = async (userId, invite) => { userId, createdAt, expires, + url: '', uses: 0, }; diff --git a/apps/meteor/app/invites/server/functions/listInvites.js b/apps/meteor/app/invites/server/functions/listInvites.ts similarity index 89% rename from apps/meteor/app/invites/server/functions/listInvites.js rename to apps/meteor/app/invites/server/functions/listInvites.ts index 1faacdcdb3e7..21e305ede246 100644 --- a/apps/meteor/app/invites/server/functions/listInvites.js +++ b/apps/meteor/app/invites/server/functions/listInvites.ts @@ -3,7 +3,7 @@ import { Invites } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -export const listInvites = async (userId) => { +export const listInvites = async (userId: string) => { if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'listInvites' }); } diff --git a/apps/meteor/app/invites/server/functions/removeInvite.js b/apps/meteor/app/invites/server/functions/removeInvite.ts similarity index 84% rename from apps/meteor/app/invites/server/functions/removeInvite.js rename to apps/meteor/app/invites/server/functions/removeInvite.ts index 8a09e307a898..9ccf725c2012 100644 --- a/apps/meteor/app/invites/server/functions/removeInvite.js +++ b/apps/meteor/app/invites/server/functions/removeInvite.ts @@ -1,9 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Invites } from '@rocket.chat/models'; +import type { IInvite } from '@rocket.chat/core-typings'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -export const removeInvite = async (userId, invite) => { +export const removeInvite = async (userId: string, invite: Pick) => { if (!userId || !invite) { return false; } diff --git a/apps/meteor/app/invites/server/functions/useInviteToken.js b/apps/meteor/app/invites/server/functions/useInviteToken.ts similarity index 85% rename from apps/meteor/app/invites/server/functions/useInviteToken.js rename to apps/meteor/app/invites/server/functions/useInviteToken.ts index d56a211e1acb..ff26ae6c9a73 100644 --- a/apps/meteor/app/invites/server/functions/useInviteToken.js +++ b/apps/meteor/app/invites/server/functions/useInviteToken.ts @@ -6,7 +6,7 @@ import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; -export const useInviteToken = async (userId, token) => { +export const useInviteToken = async (userId: string, token: string) => { if (!userId) { throw new Meteor.Error('error-invalid-user', 'The user is invalid', { method: 'useInviteToken', @@ -31,13 +31,19 @@ export const useInviteToken = async (userId, token) => { } const user = await Users.findOneById(userId); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'The user is invalid', { + method: 'useInviteToken', + field: 'userId', + }); + } await Users.updateInviteToken(user._id, token); const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 }, }); if (!subscription) { - await Invites.increaseUsageById(inviteData._id); + await Invites.increaseUsageById(inviteData._id, 1); } // If the user already has an username, then join the invite room, diff --git a/apps/meteor/app/invites/server/functions/validateInviteToken.js b/apps/meteor/app/invites/server/functions/validateInviteToken.ts similarity index 88% rename from apps/meteor/app/invites/server/functions/validateInviteToken.js rename to apps/meteor/app/invites/server/functions/validateInviteToken.ts index 623761324b30..ac12709761e0 100644 --- a/apps/meteor/app/invites/server/functions/validateInviteToken.js +++ b/apps/meteor/app/invites/server/functions/validateInviteToken.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Invites, Rooms } from '@rocket.chat/models'; -export const validateInviteToken = async (token) => { +export const validateInviteToken = async (token: string) => { if (!token || typeof token !== 'string') { throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', @@ -25,7 +25,7 @@ export const validateInviteToken = async (token) => { }); } - if (inviteData.expires && inviteData.expires <= Date.now()) { + if (inviteData.expires && new Date(inviteData.expires).getTime() <= Date.now()) { throw new Meteor.Error('error-invite-expired', 'The invite token has expired.', { method: 'validateInviteToken', field: 'expires', diff --git a/apps/meteor/app/threads/server/functions.js b/apps/meteor/app/threads/server/functions.ts similarity index 63% rename from apps/meteor/app/threads/server/functions.js rename to apps/meteor/app/threads/server/functions.ts index 5d31887b3f5b..db87a8cc62a9 100644 --- a/apps/meteor/app/threads/server/functions.js +++ b/apps/meteor/app/threads/server/functions.ts @@ -1,10 +1,12 @@ import { Messages, Subscriptions } from '@rocket.chat/models'; +import type { IMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { getMentions } from '../../lib/server/lib/notifyUsersOnMessage'; -export async function reply({ tmid }, message, parentMessage, followers) { - const { rid, ts, u, editedAt } = message; - if (!tmid || editedAt) { +export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { + const { rid, ts, u } = message; + if (!tmid || isEditedMessage(message)) { return false; } @@ -22,14 +24,14 @@ export async function reply({ tmid }, message, parentMessage, followers) { const replies = await Messages.getThreadFollowsByThreadId(tmid); - const repliesFiltered = replies.filter((userId) => userId !== u._id).filter((userId) => !mentionIds.includes(userId)); + const repliesFiltered = (replies || []).filter((userId) => userId !== u._id).filter((userId) => !mentionIds.includes(userId)); if (toAll || toHere) { await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, { groupMention: true, }); } else { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid); + await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {}); } for await (const userId of mentionIds) { @@ -37,7 +39,7 @@ export async function reply({ tmid }, message, parentMessage, followers) { } } -export async function follow({ tmid, uid }) { +export async function follow({ tmid, uid }: { tmid: string; uid: string }) { if (!tmid || !uid) { return false; } @@ -45,7 +47,7 @@ export async function follow({ tmid, uid }) { await Messages.addThreadFollowerByThreadId(tmid, uid); } -export async function unfollow({ tmid, rid, uid }) { +export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; uid: string }) { if (!tmid || !uid) { return false; } @@ -55,14 +57,18 @@ export async function unfollow({ tmid, rid, uid }) { await Messages.removeThreadFollowerByThreadId(tmid, uid); } -export const readThread = async ({ userId, rid, tmid }) => { +export const readThread = async ({ userId, rid, tmid }: { userId?: string; rid: string; tmid: string }) => { const projection = { tunread: 1 }; + if (!userId) { + return; + } + const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection }); if (!sub) { return; } // if the thread being marked as read is the last one unread also clear the unread subscription flag - const clearAlert = sub.tunread?.length <= 1 && sub.tunread.includes(tmid); + const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid); await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); }; diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index 8b073aa302ca..c47efe1944b9 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -52,7 +52,7 @@ export type EventSignatures = { 'meteor.clientVersionUpdated'(data: AutoUpdateRecord): void; 'notify.desktop'(uid: string, data: INotificationDesktop): void; 'notify.uiInteraction'(uid: string, data: IUIKitInteraction): void; - 'notify.updateInvites'(uid: string, data: { invite: IInvite }): void; + 'notify.updateInvites'(uid: string, data: { invite: Omit }): void; 'notify.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'notify.webdav'( uid: string,