From f33c07ebb8d0bb2a187f6a132e209adcf4faaed7 Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Fri, 18 Oct 2024 21:44:52 -0300 Subject: [PATCH 01/12] feat!: apply restrictions to air gapped environments (#33241) Co-authored-by: gabriellsh Co-authored-by: Guilherme Gazzo --- .changeset/little-gifts-do.md | 7 + apps/meteor/app/api/server/v1/chat.ts | 29 ++-- apps/meteor/app/api/server/v1/rooms.ts | 31 +++-- .../app/lib/server/methods/sendMessage.ts | 5 +- .../app/lib/server/methods/updateMessage.ts | 3 +- .../server/airGappedRestrictionsWrapper.ts | 5 + .../server/functions/sendUsageReport.ts} | 19 +-- .../hooks/useAirGappedRestriction.spec.ts | 52 ++++++++ .../client/hooks/useAirGappedRestriction.ts | 19 +++ apps/meteor/client/sidebar/Sidebar.tsx | 10 +- .../AirGappedRestrictionBanner.tsx | 19 +++ .../AirGappedRestrictionWarning.tsx | 27 ++++ .../sidebar/sections/BannerSection.spec.tsx | 69 ++++++++++ .../client/sidebar/sections/BannerSection.tsx | 27 ++++ ...edSection.tsx => StatusDisabledBanner.tsx} | 4 +- apps/meteor/client/sidebarv2/Sidebar.tsx | 9 +- .../AirGappedRestrictionBanner.tsx | 23 ++++ .../AirGappedRestrictionWarning.tsx | 27 ++++ .../sidebarv2/sections/BannerSection.spec.tsx | 69 ++++++++++ .../sidebarv2/sections/BannerSection.tsx | 27 ++++ ...edSection.tsx => StatusDisabledBanner.tsx} | 0 .../composer/ComposerAirGappedRestricted.tsx | 23 ++++ .../views/room/composer/ComposerContainer.tsx | 9 ++ .../license/server/airGappedRestrictions.ts | 40 ++++++ apps/meteor/ee/app/license/server/index.ts | 1 + apps/meteor/ee/app/license/server/settings.ts | 5 + .../patches/airGappedRestrictionsWrapper.ts | 10 ++ apps/meteor/ee/server/patches/index.ts | 1 + .../airGappedRestrictionsWrapper.spec.ts | 34 +++++ .../airgappedRestrictions.spec.ts | 126 ++++++++++++++++++ apps/meteor/jest.config.ts | 2 + apps/meteor/server/cron/usageReport.ts | 18 +++ apps/meteor/server/models/raw/Statistics.ts | 16 +++ apps/meteor/server/startup/cron.ts | 4 +- .../license/src/AirGappedRestriction.spec.ts | 113 ++++++++++++++++ .../license/src/AirGappedRestriction.ts | 73 ++++++++++ .../license/src/MockedLicenseBuilder.ts | 30 ++++- ee/packages/license/src/index.ts | 1 + ee/packages/license/src/token.ts | 37 ++++- packages/i18n/src/locales/en.i18n.json | 4 + .../src/MockedAppRootBuilder.tsx | 3 +- .../src/models/IStatisticsModel.ts | 1 + .../MessageFooterCallout.tsx | 1 + 43 files changed, 971 insertions(+), 62 deletions(-) create mode 100644 .changeset/little-gifts-do.md create mode 100644 apps/meteor/app/license/server/airGappedRestrictionsWrapper.ts rename apps/meteor/{server/cron/statistics.ts => app/statistics/server/functions/sendUsageReport.ts} (60%) create mode 100644 apps/meteor/client/hooks/useAirGappedRestriction.spec.ts create mode 100644 apps/meteor/client/hooks/useAirGappedRestriction.ts create mode 100644 apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx create mode 100644 apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx create mode 100644 apps/meteor/client/sidebar/sections/BannerSection.spec.tsx create mode 100644 apps/meteor/client/sidebar/sections/BannerSection.tsx rename apps/meteor/client/sidebar/sections/{StatusDisabledSection.tsx => StatusDisabledBanner.tsx} (81%) create mode 100644 apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx create mode 100644 apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx create mode 100644 apps/meteor/client/sidebarv2/sections/BannerSection.spec.tsx create mode 100644 apps/meteor/client/sidebarv2/sections/BannerSection.tsx rename apps/meteor/client/sidebarv2/sections/{StatusDisabledSection.tsx => StatusDisabledBanner.tsx} (100%) create mode 100644 apps/meteor/client/views/room/composer/ComposerAirGappedRestricted.tsx create mode 100644 apps/meteor/ee/app/license/server/airGappedRestrictions.ts create mode 100644 apps/meteor/ee/server/patches/airGappedRestrictionsWrapper.ts create mode 100644 apps/meteor/ee/tests/unit/server/airgappedRestrictions/airGappedRestrictionsWrapper.spec.ts create mode 100644 apps/meteor/ee/tests/unit/server/airgappedRestrictions/airgappedRestrictions.spec.ts create mode 100644 apps/meteor/server/cron/usageReport.ts create mode 100644 ee/packages/license/src/AirGappedRestriction.spec.ts create mode 100644 ee/packages/license/src/AirGappedRestriction.ts diff --git a/.changeset/little-gifts-do.md b/.changeset/little-gifts-do.md new file mode 100644 index 000000000000..3cdc0f2a84ac --- /dev/null +++ b/.changeset/little-gifts-do.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": major +"@rocket.chat/i18n": major +"@rocket.chat/license": major +--- + +Adds restrictions to air-gapped environments without a license diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 98c28d24581e..b5cfd9e46ce6 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -20,6 +20,7 @@ import { deleteMessageValidatingPermission } from '../../../lib/server/functions import { processWebhookMessage } from '../../../lib/server/functions/processWebhookMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; import { executeUpdateMessage } from '../../../lib/server/methods/updateMessage'; +import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper'; import { OEmbed } from '../../../oembed/server/server'; import { executeSetReaction } from '../../../reactions/server/setReaction'; import { settings } from '../../../settings/server'; @@ -160,7 +161,7 @@ API.v1.addRoute( { authRequired: true }, { async post() { - const messageReturn = (await processWebhookMessage(this.bodyParams, this.user))[0]; + const messageReturn = (await applyAirGappedRestrictionsValidation(() => processWebhookMessage(this.bodyParams, this.user)))[0]; if (!messageReturn) { return API.v1.failure('unknown-error'); @@ -218,7 +219,9 @@ API.v1.addRoute( throw new Error("Cannot send system messages using 'chat.sendMessage'"); } - const sent = await executeSendMessage(this.userId, this.bodyParams.message as Pick, this.bodyParams.previewUrls); + const sent = await applyAirGappedRestrictionsValidation(() => + executeSendMessage(this.userId, this.bodyParams.message as Pick, this.bodyParams.previewUrls), + ); const [message] = await normalizeMessagesForUser([sent], this.userId); return API.v1.success({ @@ -318,16 +321,20 @@ API.v1.addRoute( return API.v1.failure('The room id provided does not match where the message is from.'); } + const msgFromBody = this.bodyParams.text; + // Permission checks are already done in the updateMessage method, so no need to duplicate them - await executeUpdateMessage( - this.userId, - { - _id: msg._id, - msg: this.bodyParams.text, - rid: msg.rid, - customFields: this.bodyParams.customFields as Record | undefined, - }, - this.bodyParams.previewUrls, + await applyAirGappedRestrictionsValidation(() => + executeUpdateMessage( + this.userId, + { + _id: msg._id, + msg: msgFromBody, + rid: msg.rid, + customFields: this.bodyParams.customFields as Record | undefined, + }, + this.bodyParams.previewUrls, + ), ); const updatedMessage = await Messages.findOneById(msg._id); diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 4a42adf6f5bd..355cce24d40b 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -25,6 +25,7 @@ import { createDiscussion } from '../../../discussion/server/methods/createDiscu import { FileUpload } from '../../../file-upload/server'; import { sendFileMessage } from '../../../file-upload/server/methods/sendFileMessage'; import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom'; +import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper'; import { settings } from '../../../settings/server'; import { API } from '../api'; import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage'; @@ -199,7 +200,9 @@ API.v1.addRoute( delete fields.description; - await sendFileMessage(this.userId, { roomId: this.urlParams.rid, file: uploadedFile, msgData: fields }); + await applyAirGappedRestrictionsValidation(() => + sendFileMessage(this.userId, { roomId: this.urlParams.rid, file: uploadedFile, msgData: fields }), + ); const message = await Messages.getMessageByFileIdAndUsername(uploadedFile._id, this.userId); @@ -299,10 +302,8 @@ API.v1.addRoute( file.description = this.bodyParams.description; delete this.bodyParams.description; - await sendFileMessage( - this.userId, - { roomId: this.urlParams.rid, file, msgData: this.bodyParams }, - { parseAttachmentsForE2EE: false }, + await applyAirGappedRestrictionsValidation(() => + sendFileMessage(this.userId, { roomId: this.urlParams.rid, file, msgData: this.bodyParams }, { parseAttachmentsForE2EE: false }), ); await Uploads.confirmTemporaryFile(this.urlParams.fileId, this.userId); @@ -479,15 +480,17 @@ API.v1.addRoute( return API.v1.failure('Body parameter "encrypted" must be a boolean when included.'); } - const discussion = await createDiscussion(this.userId, { - prid, - pmid, - t_name, - reply, - users: users?.filter(isTruthy) || [], - encrypted, - topic, - }); + const discussion = await applyAirGappedRestrictionsValidation(() => + createDiscussion(this.userId, { + prid, + pmid, + t_name, + reply, + users: users?.filter(isTruthy) || [], + encrypted, + topic, + }), + ); return API.v1.success({ discussion }); }, diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index c75f6dbf72e2..76134c81d0b3 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -12,6 +12,7 @@ import { i18n } from '../../../../server/lib/i18n'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; import { MessageTypes } from '../../../ui-utils/server'; @@ -136,9 +137,9 @@ Meteor.methods({ } try { - return await executeSendMessage(uid, message, previewUrls); + return await applyAirGappedRestrictionsValidation(() => executeSendMessage(uid, message, previewUrls)); } catch (error: any) { - if ((error.error || error.message) === 'error-not-allowed') { + if (['error-not-allowed', 'restricted-workspace'].includes(error.error || error.message)) { throw new Meteor.Error(error.error || error.message, error.reason, { method: 'sendMessage', }); diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts index c03208a438e9..786d3555c145 100644 --- a/apps/meteor/app/lib/server/methods/updateMessage.ts +++ b/apps/meteor/app/lib/server/methods/updateMessage.ts @@ -7,6 +7,7 @@ import moment from 'moment'; import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper'; import { settings } from '../../../settings/server'; import { updateMessage } from '../functions/updateMessage'; @@ -115,6 +116,6 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateMessage' }); } - return executeUpdateMessage(uid, message, previewUrls); + return applyAirGappedRestrictionsValidation(() => executeUpdateMessage(uid, message, previewUrls)); }, }); diff --git a/apps/meteor/app/license/server/airGappedRestrictionsWrapper.ts b/apps/meteor/app/license/server/airGappedRestrictionsWrapper.ts new file mode 100644 index 000000000000..f6f05c052e4a --- /dev/null +++ b/apps/meteor/app/license/server/airGappedRestrictionsWrapper.ts @@ -0,0 +1,5 @@ +import { makeFunction } from '@rocket.chat/patch-injection'; + +export const applyAirGappedRestrictionsValidation = makeFunction(async (fn: () => Promise): Promise => { + return fn(); +}); diff --git a/apps/meteor/server/cron/statistics.ts b/apps/meteor/app/statistics/server/functions/sendUsageReport.ts similarity index 60% rename from apps/meteor/server/cron/statistics.ts rename to apps/meteor/app/statistics/server/functions/sendUsageReport.ts index 846fac2c2e51..dc684fe5fa6a 100644 --- a/apps/meteor/server/cron/statistics.ts +++ b/apps/meteor/app/statistics/server/functions/sendUsageReport.ts @@ -1,13 +1,12 @@ -import { cronJobs } from '@rocket.chat/cron'; import type { Logger } from '@rocket.chat/logger'; import { Statistics } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { Meteor } from 'meteor/meteor'; -import { getWorkspaceAccessToken } from '../../app/cloud/server'; -import { statistics } from '../../app/statistics/server'; +import { statistics } from '..'; +import { getWorkspaceAccessToken } from '../../../cloud/server'; -async function generateStatistics(logger: Logger): Promise { +export async function sendUsageReport(logger: Logger): Promise { const cronStatistics = await statistics.save(); try { @@ -27,20 +26,10 @@ async function generateStatistics(logger: Logger): Promise { if (statsToken != null) { await Statistics.updateOne({ _id: cronStatistics._id }, { $set: { statsToken } }); + return statsToken; } } catch (error) { /* error*/ logger.warn('Failed to send usage report'); } } - -export async function statsCron(logger: Logger): Promise { - const name = 'Generate and save statistics'; - await generateStatistics(logger); - - const now = new Date(); - - await cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => { - await generateStatistics(logger); - }); -} diff --git a/apps/meteor/client/hooks/useAirGappedRestriction.spec.ts b/apps/meteor/client/hooks/useAirGappedRestriction.spec.ts new file mode 100644 index 000000000000..b790fe99b24a --- /dev/null +++ b/apps/meteor/client/hooks/useAirGappedRestriction.spec.ts @@ -0,0 +1,52 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { renderHook } from '@testing-library/react'; + +import { useAirGappedRestriction } from './useAirGappedRestriction'; + +// [restricted, warning, remainingDays] +describe('useAirGappedRestriction hook', () => { + it('should return [false, false, -1] if setting value is not a number', () => { + const { result } = renderHook(() => useAirGappedRestriction(), { + legacyRoot: true, + wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1).build(), + }); + + expect(result.current).toEqual([false, false, -1]); + }); + + it('should return [false, false, -1] if user has a license (remaining days is a negative value)', () => { + const { result } = renderHook(() => useAirGappedRestriction(), { + legacyRoot: true, + wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1).build(), + }); + + expect(result.current).toEqual([false, false, -1]); + }); + + it('should return [false, false, 8] if not on warning or restriction phase', () => { + const { result } = renderHook(() => useAirGappedRestriction(), { + legacyRoot: true, + wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 8).build(), + }); + + expect(result.current).toEqual([false, false, 8]); + }); + + it('should return [true, false, 7] if on warning phase', () => { + const { result } = renderHook(() => useAirGappedRestriction(), { + legacyRoot: true, + wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 7).build(), + }); + + expect(result.current).toEqual([false, true, 7]); + }); + + it('should return [true, false, 0] if on restriction phase', () => { + const { result } = renderHook(() => useAirGappedRestriction(), { + legacyRoot: true, + wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0).build(), + }); + + expect(result.current).toEqual([true, false, 0]); + }); +}); diff --git a/apps/meteor/client/hooks/useAirGappedRestriction.ts b/apps/meteor/client/hooks/useAirGappedRestriction.ts new file mode 100644 index 000000000000..fbb502d5b49d --- /dev/null +++ b/apps/meteor/client/hooks/useAirGappedRestriction.ts @@ -0,0 +1,19 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; + +export const useAirGappedRestriction = (): [isRestrictionPhase: boolean, isWarningPhase: boolean, remainingDays: number] => { + const airGappedRestrictionRemainingDays = useSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days'); + + if (typeof airGappedRestrictionRemainingDays !== 'number') { + return [false, false, -1]; + } + + // If this value is negative, the user has a license with valid module + if (airGappedRestrictionRemainingDays < 0) { + return [false, false, airGappedRestrictionRemainingDays]; + } + + const isRestrictionPhase = airGappedRestrictionRemainingDays === 0; + const isWarningPhase = !isRestrictionPhase && airGappedRestrictionRemainingDays <= 7; + + return [isRestrictionPhase, isWarningPhase, airGappedRestrictionRemainingDays]; +}; diff --git a/apps/meteor/client/sidebar/Sidebar.tsx b/apps/meteor/client/sidebar/Sidebar.tsx index b947554f4f3b..683013b38213 100644 --- a/apps/meteor/client/sidebar/Sidebar.tsx +++ b/apps/meteor/client/sidebar/Sidebar.tsx @@ -1,24 +1,22 @@ import { css } from '@rocket.chat/css-in-js'; import { Box } from '@rocket.chat/fuselage'; -import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; -import { useLayout, useSetting, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useLayout, useUserPreference } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; import { useOmnichannelEnabled } from '../hooks/omnichannel/useOmnichannelEnabled'; import SidebarRoomList from './RoomList'; import SidebarFooter from './footer'; import SidebarHeader from './header'; +import BannerSection from './sections/BannerSection'; import OmnichannelSection from './sections/OmnichannelSection'; -import StatusDisabledSection from './sections/StatusDisabledSection'; +// TODO unit test airgappedbanner const Sidebar = () => { const showOmnichannel = useOmnichannelEnabled(); const sidebarViewMode = useUserPreference('sidebarViewMode'); const sidebarHideAvatar = !useUserPreference('sidebarDisplayAvatar'); const { sidebar } = useLayout(); - const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); - const presenceDisabled = useSetting('Presence_broadcast_disabled'); const sidebarLink = css` a { @@ -41,7 +39,7 @@ const Sidebar = () => { data-qa-opened={sidebar.isCollapsed ? 'false' : 'true'} > - {presenceDisabled && !bannerDismissed && setBannerDismissed(true)} />} + {showOmnichannel && } diff --git a/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx b/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx new file mode 100644 index 000000000000..dca296a2ea19 --- /dev/null +++ b/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx @@ -0,0 +1,19 @@ +import { SidebarBanner } from '@rocket.chat/fuselage'; +import { ExternalLink } from '@rocket.chat/ui-client'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import AirGappedRestrictionWarning from './AirGappedRestrictionWarning'; + +const AirGappedRestrictionSection = ({ isRestricted, remainingDays }: { isRestricted: boolean; remainingDays: number }) => { + const { t } = useTranslation(); + + return ( + } + description={{t('Learn_more')}} + /> + ); +}; + +export default AirGappedRestrictionSection; diff --git a/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx b/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx new file mode 100644 index 000000000000..d6db0abbf9fd --- /dev/null +++ b/apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx @@ -0,0 +1,27 @@ +import { Box } from '@rocket.chat/fuselage'; +import React from 'react'; +import { Trans } from 'react-i18next'; + +const AirGappedRestrictionWarning = ({ isRestricted, remainingDays }: { isRestricted: boolean; remainingDays: number }) => { + if (isRestricted) { + return ( + + This air-gapped workspace is in read-only mode.{' '} + + Connect it to internet or upgraded to a premium plan to restore full functionality. + + + ); + } + + return ( + + This air-gapped workspace will enter read-only mode in <>{{ remainingDays }} days.{' '} + + Connect it to internet or upgrade to a premium plan to prevent this. + + + ); +}; + +export default AirGappedRestrictionWarning; diff --git a/apps/meteor/client/sidebar/sections/BannerSection.spec.tsx b/apps/meteor/client/sidebar/sections/BannerSection.spec.tsx new file mode 100644 index 000000000000..9486d34a17bc --- /dev/null +++ b/apps/meteor/client/sidebar/sections/BannerSection.spec.tsx @@ -0,0 +1,69 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import BannerSection from './BannerSection'; + +// TODO: test presence banner +describe('Sidebar -> BannerSection -> Airgapped restriction', () => { + it('Should render null if restricted and not admin', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .build(), + }); + + expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument(); + }); + + it('Should render null if admin and not restricted or warning', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot().withJohnDoe().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 8).build(), + }); + + expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument(); + }); + + it('Should render warning message if admin and warning phase', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 7) + .build(), + }); + + expect(screen.getByText('will enter read-only', { exact: false })).toBeInTheDocument(); + }); + + it('Should render restriction message if admin and restricted phase', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .build(), + }); + + expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument(); + }); + + it('Should render restriction message instead of another banner', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .withSetting('Presence_broadcast_disabled', true) + .build(), + }); + + expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument(); + }); +}); diff --git a/apps/meteor/client/sidebar/sections/BannerSection.tsx b/apps/meteor/client/sidebar/sections/BannerSection.tsx new file mode 100644 index 000000000000..f6ce2ac835c0 --- /dev/null +++ b/apps/meteor/client/sidebar/sections/BannerSection.tsx @@ -0,0 +1,27 @@ +import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; +import { useRole, useSetting } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { useAirGappedRestriction } from '../../hooks/useAirGappedRestriction'; +import AirGappedRestrictionBanner from './AirGappedRestrictionBanner/AirGappedRestrictionBanner'; +import StatusDisabledBanner from './StatusDisabledBanner'; + +const BannerSection = () => { + const [isRestricted, isWarning, remainingDays] = useAirGappedRestriction(); + const isAdmin = useRole('admin'); + + const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); + const presenceDisabled = useSetting('Presence_broadcast_disabled'); + + if ((isWarning || isRestricted) && isAdmin) { + return ; + } + + if (presenceDisabled && !bannerDismissed) { + return setBannerDismissed(true)} />; + } + + return null; +}; + +export default BannerSection; diff --git a/apps/meteor/client/sidebar/sections/StatusDisabledSection.tsx b/apps/meteor/client/sidebar/sections/StatusDisabledBanner.tsx similarity index 81% rename from apps/meteor/client/sidebar/sections/StatusDisabledSection.tsx rename to apps/meteor/client/sidebar/sections/StatusDisabledBanner.tsx index c8f56ffe5458..f9525ec93337 100644 --- a/apps/meteor/client/sidebar/sections/StatusDisabledSection.tsx +++ b/apps/meteor/client/sidebar/sections/StatusDisabledBanner.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useStatusDisabledModal } from '../../views/admin/customUserStatus/hooks/useStatusDisabledModal'; -const StatusDisabledSection = ({ onDismiss }: { onDismiss: () => void }) => { +const StatusDisabledBanner = ({ onDismiss }: { onDismiss: () => void }) => { const { t } = useTranslation(); const handleStatusDisabledModal = useStatusDisabledModal(); @@ -18,4 +18,4 @@ const StatusDisabledSection = ({ onDismiss }: { onDismiss: () => void }) => { ); }; -export default StatusDisabledSection; +export default StatusDisabledBanner; diff --git a/apps/meteor/client/sidebarv2/Sidebar.tsx b/apps/meteor/client/sidebarv2/Sidebar.tsx index 7209f51507d9..8038bf8b1238 100644 --- a/apps/meteor/client/sidebarv2/Sidebar.tsx +++ b/apps/meteor/client/sidebarv2/Sidebar.tsx @@ -1,18 +1,15 @@ import { SidebarV2 } from '@rocket.chat/fuselage'; -import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; -import { useSetting, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useUserPreference } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; import SidebarRoomList from './RoomList'; import SidebarFooter from './footer'; import SearchSection from './header/SearchSection'; -import StatusDisabledSection from './sections/StatusDisabledSection'; +import BannerSection from './sections/BannerSection'; const Sidebar = () => { const sidebarViewMode = useUserPreference('sidebarViewMode'); const sidebarHideAvatar = !useUserPreference('sidebarDisplayAvatar'); - const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); - const presenceDisabled = useSetting('Presence_broadcast_disabled'); return ( { .join(' ')} > - {presenceDisabled && !bannerDismissed && setBannerDismissed(true)} />} + diff --git a/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx b/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx new file mode 100644 index 000000000000..ff8c53bf484a --- /dev/null +++ b/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionBanner.tsx @@ -0,0 +1,23 @@ +import { SidebarV2Banner } from '@rocket.chat/fuselage'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import AirGappedRestrictionWarning from './AirGappedRestrictionWarning'; + +const AirGappedRestrictionSection = ({ isRestricted, remainingDays }: { isRestricted: boolean; remainingDays: number }) => { + const { t } = useTranslation(); + + return ( + } + linkText={t('Learn_more')} + linkProps={{ + target: '_blank', + rel: 'noopener noreferrer', + href: 'https://go.rocket.chat/i/airgapped-restriction', + }} + /> + ); +}; + +export default AirGappedRestrictionSection; diff --git a/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx b/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx new file mode 100644 index 000000000000..5c1555c8408b --- /dev/null +++ b/apps/meteor/client/sidebarv2/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx @@ -0,0 +1,27 @@ +import { Box } from '@rocket.chat/fuselage'; +import React from 'react'; +import { Trans } from 'react-i18next'; + +const AirGappedRestrictionWarning = ({ isRestricted, remainingDays }: { isRestricted: boolean; remainingDays: number }) => { + if (isRestricted) { + return ( + + This air-gapped workspace is in read-only mode.{' '} + + Connect it to internet or upgraded to a premium plan to restore full functionality. + + + ); + } + + return ( + + This air-gapped workspace will enter read-only mode in <>{{ remainingDays }} days.{' '} + + Connect it to internet or upgrade to a premium plan to prevent this. + + + ); +}; + +export default AirGappedRestrictionWarning; diff --git a/apps/meteor/client/sidebarv2/sections/BannerSection.spec.tsx b/apps/meteor/client/sidebarv2/sections/BannerSection.spec.tsx new file mode 100644 index 000000000000..9486d34a17bc --- /dev/null +++ b/apps/meteor/client/sidebarv2/sections/BannerSection.spec.tsx @@ -0,0 +1,69 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import BannerSection from './BannerSection'; + +// TODO: test presence banner +describe('Sidebar -> BannerSection -> Airgapped restriction', () => { + it('Should render null if restricted and not admin', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .build(), + }); + + expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument(); + }); + + it('Should render null if admin and not restricted or warning', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot().withJohnDoe().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 8).build(), + }); + + expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument(); + }); + + it('Should render warning message if admin and warning phase', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 7) + .build(), + }); + + expect(screen.getByText('will enter read-only', { exact: false })).toBeInTheDocument(); + }); + + it('Should render restriction message if admin and restricted phase', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .build(), + }); + + expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument(); + }); + + it('Should render restriction message instead of another banner', () => { + render(, { + legacyRoot: true, + wrapper: mockAppRoot() + .withJohnDoe() + .withRole('admin') + .withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0) + .withSetting('Presence_broadcast_disabled', true) + .build(), + }); + + expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument(); + }); +}); diff --git a/apps/meteor/client/sidebarv2/sections/BannerSection.tsx b/apps/meteor/client/sidebarv2/sections/BannerSection.tsx new file mode 100644 index 000000000000..f6ce2ac835c0 --- /dev/null +++ b/apps/meteor/client/sidebarv2/sections/BannerSection.tsx @@ -0,0 +1,27 @@ +import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; +import { useRole, useSetting } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { useAirGappedRestriction } from '../../hooks/useAirGappedRestriction'; +import AirGappedRestrictionBanner from './AirGappedRestrictionBanner/AirGappedRestrictionBanner'; +import StatusDisabledBanner from './StatusDisabledBanner'; + +const BannerSection = () => { + const [isRestricted, isWarning, remainingDays] = useAirGappedRestriction(); + const isAdmin = useRole('admin'); + + const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); + const presenceDisabled = useSetting('Presence_broadcast_disabled'); + + if ((isWarning || isRestricted) && isAdmin) { + return ; + } + + if (presenceDisabled && !bannerDismissed) { + return setBannerDismissed(true)} />; + } + + return null; +}; + +export default BannerSection; diff --git a/apps/meteor/client/sidebarv2/sections/StatusDisabledSection.tsx b/apps/meteor/client/sidebarv2/sections/StatusDisabledBanner.tsx similarity index 100% rename from apps/meteor/client/sidebarv2/sections/StatusDisabledSection.tsx rename to apps/meteor/client/sidebarv2/sections/StatusDisabledBanner.tsx diff --git a/apps/meteor/client/views/room/composer/ComposerAirGappedRestricted.tsx b/apps/meteor/client/views/room/composer/ComposerAirGappedRestricted.tsx new file mode 100644 index 000000000000..64cf1e0a9a7e --- /dev/null +++ b/apps/meteor/client/views/room/composer/ComposerAirGappedRestricted.tsx @@ -0,0 +1,23 @@ +import { Box, Icon } from '@rocket.chat/fuselage'; +import { MessageFooterCallout } from '@rocket.chat/ui-composer'; +import type { ReactElement } from 'react'; +import React from 'react'; +import { Trans } from 'react-i18next'; + +const ComposerAirGappedRestricted = (): ReactElement => { + return ( + + + + + + Workspace in read-only mode. + + Admins can restore full functionality by connecting it to internet or upgrading to a premium plan. + + + + ); +}; + +export default ComposerAirGappedRestricted; diff --git a/apps/meteor/client/views/room/composer/ComposerContainer.tsx b/apps/meteor/client/views/room/composer/ComposerContainer.tsx index 7d3e09847b34..0e870067133f 100644 --- a/apps/meteor/client/views/room/composer/ComposerContainer.tsx +++ b/apps/meteor/client/views/room/composer/ComposerContainer.tsx @@ -3,7 +3,9 @@ import { usePermission } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; +import { useAirGappedRestriction } from '../../../hooks/useAirGappedRestriction'; import { useRoom } from '../contexts/RoomContext'; +import ComposerAirGappedRestricted from './ComposerAirGappedRestricted'; import ComposerAnonymous from './ComposerAnonymous'; import ComposerArchived from './ComposerArchived'; import ComposerBlocked from './ComposerBlocked'; @@ -21,6 +23,7 @@ import { useMessageComposerIsReadOnly } from './hooks/useMessageComposerIsReadOn const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactElement => { const room = useRoom(); + const canJoinWithoutCode = usePermission('join-without-join-code'); const mustJoinWithCode = !props.subscription && room.joinCodeRequired && !canJoinWithoutCode; @@ -33,6 +36,12 @@ const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactE const isFederation = isRoomFederated(room); const isVoip = isVoipRoom(room); + const [isAirGappedRestricted] = useAirGappedRestriction(); + + if (isAirGappedRestricted) { + return ; + } + if (isOmnichannel) { return ; } diff --git a/apps/meteor/ee/app/license/server/airGappedRestrictions.ts b/apps/meteor/ee/app/license/server/airGappedRestrictions.ts new file mode 100644 index 000000000000..01a15e72e820 --- /dev/null +++ b/apps/meteor/ee/app/license/server/airGappedRestrictions.ts @@ -0,0 +1,40 @@ +import { AirGappedRestriction, License } from '@rocket.chat/license'; +import { Settings, Statistics } from '@rocket.chat/models'; + +import { notifyOnSettingChangedById } from '../../../../app/lib/server/lib/notifyListener'; +import { i18n } from '../../../../server/lib/i18n'; +import { sendMessagesToAdmins } from '../../../../server/lib/sendMessagesToAdmins'; + +const updateRestrictionSetting = async (remainingDays: number) => { + await Settings.updateValueById('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', remainingDays); + void notifyOnSettingChangedById('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days'); +}; + +const sendRocketCatWarningToAdmins = async (remainingDays: number) => { + const lastDayOrNoRestrictionsAtAll = remainingDays <= 0; + if (lastDayOrNoRestrictionsAtAll) { + return; + } + if (AirGappedRestriction.isWarningPeriod(remainingDays)) { + await sendMessagesToAdmins({ + msgs: async ({ adminUser }) => ({ + msg: i18n.t('AirGapped_Restriction_Warning', { lng: adminUser.language || 'en', remainingDays }), + }), + }); + } +}; + +AirGappedRestriction.on('remainingDays', async ({ days }: { days: number }) => { + await updateRestrictionSetting(days); + await sendRocketCatWarningToAdmins(days); +}); + +License.onValidateLicense(async () => { + const token = await Statistics.findLastStatsToken(); + void AirGappedRestriction.computeRestriction(token); +}); + +License.onRemoveLicense(async () => { + const token = await Statistics.findLastStatsToken(); + void AirGappedRestriction.computeRestriction(token); +}); diff --git a/apps/meteor/ee/app/license/server/index.ts b/apps/meteor/ee/app/license/server/index.ts index 9177532a9e21..efef260a6cb0 100644 --- a/apps/meteor/ee/app/license/server/index.ts +++ b/apps/meteor/ee/app/license/server/index.ts @@ -1,2 +1,3 @@ import './settings'; import './methods'; +import './airGappedRestrictions'; diff --git a/apps/meteor/ee/app/license/server/settings.ts b/apps/meteor/ee/app/license/server/settings.ts index 590a937fe18f..045c74e331e4 100644 --- a/apps/meteor/ee/app/license/server/settings.ts +++ b/apps/meteor/ee/app/license/server/settings.ts @@ -20,4 +20,9 @@ await settingsRegistry.addGroup('Enterprise', async function () { i18nLabel: 'Status', }); }); + await this.add('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1, { + type: 'int', + readonly: true, + public: true, + }); }); diff --git a/apps/meteor/ee/server/patches/airGappedRestrictionsWrapper.ts b/apps/meteor/ee/server/patches/airGappedRestrictionsWrapper.ts new file mode 100644 index 000000000000..dabafe87c9fc --- /dev/null +++ b/apps/meteor/ee/server/patches/airGappedRestrictionsWrapper.ts @@ -0,0 +1,10 @@ +import { AirGappedRestriction } from '@rocket.chat/license'; + +import { applyAirGappedRestrictionsValidation } from '../../../app/license/server/airGappedRestrictionsWrapper'; + +applyAirGappedRestrictionsValidation.patch(async (_: any, fn: () => Promise): Promise => { + if (AirGappedRestriction.restricted) { + throw new Error('restricted-workspace'); + } + return fn(); +}); diff --git a/apps/meteor/ee/server/patches/index.ts b/apps/meteor/ee/server/patches/index.ts index ab3f4bf147c2..9e6cab1e0924 100644 --- a/apps/meteor/ee/server/patches/index.ts +++ b/apps/meteor/ee/server/patches/index.ts @@ -1,3 +1,4 @@ import './closeBusinessHour'; import './getInstanceList'; import './isDepartmentCreationAvailable'; +import './airGappedRestrictionsWrapper'; diff --git a/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airGappedRestrictionsWrapper.spec.ts b/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airGappedRestrictionsWrapper.spec.ts new file mode 100644 index 000000000000..982efe01158e --- /dev/null +++ b/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airGappedRestrictionsWrapper.spec.ts @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import proxyquire from 'proxyquire'; +import sinon from 'sinon'; + +import { applyAirGappedRestrictionsValidation } from '../../../../../app/license/server/airGappedRestrictionsWrapper'; + +let restrictionFlag = true; + +const airgappedModule = { + get restricted() { + return restrictionFlag; + }, +}; + +proxyquire.noCallThru().load('../../../../server/patches/airGappedRestrictionsWrapper.ts', { + '@rocket.chat/license': { + AirGappedRestriction: airgappedModule, + }, + '../../../app/license/server/airGappedRestrictionsWrapper': { + applyAirGappedRestrictionsValidation, + }, +}); + +describe('#airGappedRestrictionsWrapper()', () => { + it('should throw an error when the workspace is restricted', async () => { + await expect(applyAirGappedRestrictionsValidation(sinon.stub())).to.be.rejectedWith('restricted-workspace'); + }); + it('should NOT throw an error when the workspace is not restricted', async () => { + restrictionFlag = false; + const spy = sinon.stub(); + await expect(applyAirGappedRestrictionsValidation(spy)).to.eventually.equal(undefined); + expect(spy.calledOnce).to.be.true; + }); +}); diff --git a/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airgappedRestrictions.spec.ts b/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airgappedRestrictions.spec.ts new file mode 100644 index 000000000000..a1aace7ae7dd --- /dev/null +++ b/apps/meteor/ee/tests/unit/server/airgappedRestrictions/airgappedRestrictions.spec.ts @@ -0,0 +1,126 @@ +import { Emitter } from '@rocket.chat/emitter'; +import { expect } from 'chai'; +import proxyquire from 'proxyquire'; +import sinon from 'sinon'; + +let promises: Array> = []; + +class AirgappedRestriction extends Emitter<{ remainingDays: { days: number } }> { + computeRestriction = sinon.spy(); + + isWarningPeriod = sinon.stub(); + + on(type: any, cb: any): any { + const newCb = (...args: any) => { + promises.push(cb(...args)); + }; + return super.on(type, newCb); + } +} + +const airgappedRestrictionObj = new AirgappedRestriction(); + +const mocks = { + sendMessagesToAdmins: ({ msgs }: any) => { + msgs({ adminUser: { language: 'pt-br' } }); + }, + settingsUpdate: sinon.spy(), + notifySetting: sinon.spy(), + i18n: sinon.spy(), + findLastToken: sinon.stub(), +}; + +const licenseMock = { + validateCb: async () => undefined, + removeCb: async () => undefined, + onValidateLicense: async (cb: any) => { + licenseMock.validateCb = cb; + }, + onRemoveLicense: async (cb: any) => { + licenseMock.removeCb = cb; + }, +}; + +proxyquire.noCallThru().load('../../../../app/license/server/airGappedRestrictions.ts', { + '@rocket.chat/license': { + AirGappedRestriction: airgappedRestrictionObj, + License: licenseMock, + }, + '@rocket.chat/models': { + Settings: { + updateValueById: mocks.settingsUpdate, + }, + Statistics: { + findLastStatsToken: mocks.findLastToken, + }, + }, + '../../../../app/lib/server/lib/notifyListener': { + notifyOnSettingChangedById: mocks.notifySetting, + }, + '../../../../server/lib/i18n': { + i18n: { + t: mocks.i18n, + }, + }, + '../../../../server/lib/sendMessagesToAdmins': { + sendMessagesToAdmins: mocks.sendMessagesToAdmins, + }, +}); + +describe('airgappedRestrictions', () => { + afterEach(() => { + Object.values(mocks).forEach((mock) => { + if ('resetHistory' in mock) { + mock.resetHistory(); + } + if ('reset' in mock) { + mock.reset(); + } + }); + airgappedRestrictionObj.computeRestriction.resetHistory(); + airgappedRestrictionObj.isWarningPeriod.reset(); + promises = []; + }); + it('should update setting when restriction is removed', async () => { + airgappedRestrictionObj.emit('remainingDays', { days: -1 }); + + await Promise.all(promises); + expect(mocks.settingsUpdate.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1)).to.be.true; + expect(mocks.notifySetting.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days')).to.be.true; + expect(airgappedRestrictionObj.isWarningPeriod.called).to.be.false; + }); + + it('should update setting when restriction is applied', async () => { + airgappedRestrictionObj.emit('remainingDays', { days: 0 }); + + await Promise.all(promises); + expect(mocks.settingsUpdate.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0)).to.be.true; + expect(mocks.notifySetting.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days')).to.be.true; + expect(airgappedRestrictionObj.isWarningPeriod.called).to.be.false; + }); + + it('should update setting and send rocket.cat message when in warning period', async () => { + airgappedRestrictionObj.emit('remainingDays', { days: 1 }); + airgappedRestrictionObj.isWarningPeriod.returns(true); + + await Promise.all(promises); + expect(mocks.settingsUpdate.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 1)).to.be.true; + expect(mocks.notifySetting.calledWith('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days')).to.be.true; + expect(airgappedRestrictionObj.isWarningPeriod.called).to.be.true; + expect(mocks.i18n.calledWith('AirGapped_Restriction_Warning', { lng: 'pt-br' })); + }); + + it('should recompute restriction if license is applied', async () => { + mocks.findLastToken.returns('token'); + await licenseMock.validateCb(); + expect(mocks.findLastToken.calledOnce).to.be.true; + expect(airgappedRestrictionObj.computeRestriction.calledWith('token')).to.be.true; + }); + + it('should recompute restriction if license is removed', async () => { + mocks.findLastToken.returns('token'); + await licenseMock.removeCb(); + expect(mocks.findLastToken.calledOnce).to.be.true; + expect(airgappedRestrictionObj.computeRestriction.calledWith('token')).to.be.true; + }); +}); diff --git a/apps/meteor/jest.config.ts b/apps/meteor/jest.config.ts index b944cd795620..190ad361d4ad 100644 --- a/apps/meteor/jest.config.ts +++ b/apps/meteor/jest.config.ts @@ -35,6 +35,8 @@ export default { '/app/livechat/server/business-hour/**/*.spec.ts?(x)', '/app/livechat/server/api/**/*.spec.ts', '/ee/app/authorization/server/validateUserRoles.spec.ts', + '/ee/app/license/server/**/*.spec.ts', + '/ee/server/patches/**/*.spec.ts', '/app/cloud/server/functions/supportedVersionsToken/**.spec.ts', '/app/utils/lib/**.spec.ts', ], diff --git a/apps/meteor/server/cron/usageReport.ts b/apps/meteor/server/cron/usageReport.ts new file mode 100644 index 000000000000..c774335c9334 --- /dev/null +++ b/apps/meteor/server/cron/usageReport.ts @@ -0,0 +1,18 @@ +import { cronJobs } from '@rocket.chat/cron'; +import { AirGappedRestriction } from '@rocket.chat/license'; +import type { Logger } from '@rocket.chat/logger'; + +import { sendUsageReport } from '../../app/statistics/server/functions/sendUsageReport'; + +export async function usageReportCron(logger: Logger): Promise { + const name = 'Generate and save statistics'; + const statsToken = await sendUsageReport(logger); + void AirGappedRestriction.computeRestriction(statsToken); + + const now = new Date(); + + await cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => { + const statsToken = await sendUsageReport(logger); + void AirGappedRestriction.computeRestriction(statsToken); + }); +} diff --git a/apps/meteor/server/models/raw/Statistics.ts b/apps/meteor/server/models/raw/Statistics.ts index bad44ee07c23..310d87f07d5d 100644 --- a/apps/meteor/server/models/raw/Statistics.ts +++ b/apps/meteor/server/models/raw/Statistics.ts @@ -26,6 +26,22 @@ export class StatisticsRaw extends BaseRaw implements IStatisticsModel { return records?.[0]; } + async findLastStatsToken(): Promise { + const records = await this.find( + {}, + { + sort: { + createdAt: -1, + }, + projection: { + statsToken: 1, + }, + limit: 1, + }, + ).toArray(); + return records?.[0]?.statsToken; + } + async findMonthlyPeakConnections() { const oneMonthAgo = new Date(); oneMonthAgo.setDate(oneMonthAgo.getDate() - 30); diff --git a/apps/meteor/server/startup/cron.ts b/apps/meteor/server/startup/cron.ts index 6057186a1e4e..308d1297a01a 100644 --- a/apps/meteor/server/startup/cron.ts +++ b/apps/meteor/server/startup/cron.ts @@ -5,8 +5,8 @@ import { federationCron } from '../cron/federation'; import { npsCron } from '../cron/nps'; import { oembedCron } from '../cron/oembed'; import { startCron } from '../cron/start'; -import { statsCron } from '../cron/statistics'; import { temporaryUploadCleanupCron } from '../cron/temporaryUploadsCleanup'; +import { usageReportCron } from '../cron/usageReport'; import { userDataDownloadsCron } from '../cron/userDataDownloads'; import { videoConferencesCron } from '../cron/videoConferences'; @@ -16,7 +16,7 @@ Meteor.defer(async () => { await startCron(); await oembedCron(); - await statsCron(logger); + await usageReportCron(logger); await npsCron(); await temporaryUploadCleanupCron(); await federationCron(); diff --git a/ee/packages/license/src/AirGappedRestriction.spec.ts b/ee/packages/license/src/AirGappedRestriction.spec.ts new file mode 100644 index 000000000000..960e170c5b16 --- /dev/null +++ b/ee/packages/license/src/AirGappedRestriction.spec.ts @@ -0,0 +1,113 @@ +import { AirGappedRestriction } from './AirGappedRestriction'; +import { StatsTokenBuilder } from './MockedLicenseBuilder'; +import { License } from './licenseImp'; + +jest.mock('./licenseImp', () => ({ + License: { + hasValidLicense: jest.fn().mockReturnValue(false), + }, +})); + +describe('AirGappedRestriction', () => { + describe('#computeRestriction()', () => { + it('should notify remaining days = 0 (apply restriction) when token is not a string', async () => { + const handler = jest.fn(); + + AirGappedRestriction.on('remainingDays', handler); + await AirGappedRestriction.computeRestriction(); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: 0, + }); + expect(AirGappedRestriction.restricted).toBe(true); + }); + it('should notify remaining days = 0 (apply restriction) when it was not possible to decrypt the stats token', async () => { + const handler = jest.fn(); + + AirGappedRestriction.on('remainingDays', handler); + await AirGappedRestriction.computeRestriction('invalid-token'); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: 0, + }); + expect(AirGappedRestriction.restricted).toBe(true); + }); + it('should notify remaining days (8) within the accepted range (1 - 10) when the last reported stats happened 2 days ago', async () => { + const now = new Date(); + now.setDate(now.getDate() - 2); + const token = await new StatsTokenBuilder().withTimeStamp(now).sign(); + const handler = jest.fn(); + + AirGappedRestriction.on('remainingDays', handler); + await AirGappedRestriction.computeRestriction(token); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: 8, + }); + expect(AirGappedRestriction.restricted).toBe(false); + }); + it('should notify remaining days = 0 (apply restrictions) when the last reported stats happened more than 10 days ago', async () => { + const now = new Date(); + now.setDate(now.getDate() - 11); + const token = await new StatsTokenBuilder().withTimeStamp(now).sign(); + const handler = jest.fn(); + + AirGappedRestriction.on('remainingDays', handler); + await AirGappedRestriction.computeRestriction(token); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: 0, + }); + expect(AirGappedRestriction.restricted).toBe(true); + }); + it('should notify remaining days = 0 (apply restrictions) when the last reported stats happened 10 days ago', async () => { + const now = new Date(); + now.setDate(now.getDate() - 10); + const token = await new StatsTokenBuilder().withTimeStamp(now).sign(); + const handler = jest.fn(); + + AirGappedRestriction.on('remainingDays', handler); + await AirGappedRestriction.computeRestriction(token); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: 0, + }); + expect(AirGappedRestriction.restricted).toBe(true); + }); + it('should notify remaining days = -1 when has valid license', () => { + const handler = jest.fn(); + (License.hasValidLicense as jest.Mock).mockReturnValueOnce(true); + + AirGappedRestriction.on('remainingDays', handler); + AirGappedRestriction.computeRestriction(); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + days: -1, + }); + expect(AirGappedRestriction.restricted).toBe(false); + }); + }); + describe('#isWarningPeriod', () => { + it('should return true if value is between or exactly 0 and 7', async () => { + expect(AirGappedRestriction.isWarningPeriod(0)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(1)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(2)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(3)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(4)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(5)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(6)).toBe(true); + expect(AirGappedRestriction.isWarningPeriod(7)).toBe(true); + }); + it('should return false if value is lesser than 0 or bigger than 7', async () => { + expect(AirGappedRestriction.isWarningPeriod(-1)).toBe(false); + expect(AirGappedRestriction.isWarningPeriod(8)).toBe(false); + expect(AirGappedRestriction.isWarningPeriod(10)).toBe(false); + }); + }); +}); diff --git a/ee/packages/license/src/AirGappedRestriction.ts b/ee/packages/license/src/AirGappedRestriction.ts new file mode 100644 index 000000000000..1ba3bb1b3299 --- /dev/null +++ b/ee/packages/license/src/AirGappedRestriction.ts @@ -0,0 +1,73 @@ +import EventEmitter from 'events'; + +import { License } from '.'; +import { decryptStatsToken } from './token'; + +const DAY_IN_MS = 24 * 60 * 60 * 1000; +const NO_ACTION_PERIOD_IN_DAYS = 3; +const WARNING_PERIOD_IN_DAYS = 7; + +class AirGappedRestrictionClass extends EventEmitter { + #restricted = true; + + public get restricted(): boolean { + return this.#restricted; + } + + public async computeRestriction(encryptedToken?: string): Promise { + if (License.hasValidLicense()) { + this.removeRestrictionsUnderLicense(); + return; + } + + if (typeof encryptedToken !== 'string') { + this.applyRestrictions(); + return; + } + + return this.checkRemainingDaysSinceLastStatsReport(encryptedToken); + } + + private async checkRemainingDaysSinceLastStatsReport(encryptedToken: string): Promise { + try { + const { timestamp: lastStatsReportTimestamp } = JSON.parse(await decryptStatsToken(encryptedToken)); + const now = new Date(); + const lastStatsReport = new Date(lastStatsReportTimestamp); + const nowUTC = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()); + const lastStatsReportUTC = Date.UTC(lastStatsReport.getFullYear(), lastStatsReport.getMonth(), lastStatsReport.getDate()); + + const daysSinceLastStatsReport = Math.floor((nowUTC - lastStatsReportUTC) / DAY_IN_MS); + + this.notifyRemainingDaysUntilRestriction(daysSinceLastStatsReport); + } catch { + this.applyRestrictions(); + } + } + + private applyRestrictions(): void { + this.notifyRemainingDaysUntilRestriction(NO_ACTION_PERIOD_IN_DAYS + WARNING_PERIOD_IN_DAYS); + } + + private removeRestrictionsUnderLicense(): void { + this.#restricted = false; + this.emit('remainingDays', { days: -1 }); + } + + public isWarningPeriod(days: number) { + if (days < 0) { + return false; + } + return days <= WARNING_PERIOD_IN_DAYS; + } + + private notifyRemainingDaysUntilRestriction(daysSinceLastStatsReport: number): void { + const remainingDaysUntilRestriction = Math.max(NO_ACTION_PERIOD_IN_DAYS + WARNING_PERIOD_IN_DAYS - daysSinceLastStatsReport, 0); + + this.#restricted = remainingDaysUntilRestriction === 0; + this.emit('remainingDays', { days: remainingDaysUntilRestriction }); + } +} + +const airGappedRestriction = new AirGappedRestrictionClass(); + +export { airGappedRestriction as AirGappedRestriction }; diff --git a/ee/packages/license/src/MockedLicenseBuilder.ts b/ee/packages/license/src/MockedLicenseBuilder.ts index d9def5b6b0d5..a0ae1a37f9a3 100644 --- a/ee/packages/license/src/MockedLicenseBuilder.ts +++ b/ee/packages/license/src/MockedLicenseBuilder.ts @@ -10,7 +10,7 @@ import { type Timestamp, } from '@rocket.chat/core-typings'; -import { encrypt } from './token'; +import { encrypt, encryptStatsToken } from './token'; export class MockedLicenseBuilder { information: { @@ -241,3 +241,31 @@ export class MockedLicenseBuilder { return encrypt(this.build()); } } + +export class StatsTokenBuilder { + private token: Record; + + constructor() { + this.token = { + workspaceId: '123456789', + uniqueId: '123456789', + recordId: '123456789', + timestamp: new Date().toISOString(), + info: {}, + }; + } + + public withTimeStamp(date: Date): StatsTokenBuilder { + this.token.timestamp = date.toISOString(); + + return this; + } + + public build(): Record { + return this.token; + } + + public sign(): Promise { + return encryptStatsToken(this.token); + } +} diff --git a/ee/packages/license/src/index.ts b/ee/packages/license/src/index.ts index 3536d572105d..bb816ea4183b 100644 --- a/ee/packages/license/src/index.ts +++ b/ee/packages/license/src/index.ts @@ -2,3 +2,4 @@ export { DuplicatedLicenseError } from './errors/DuplicatedLicenseError'; export * from './licenseImp'; export * from './MockedLicenseBuilder'; export * from './applyLicense'; +export * from './AirGappedRestriction'; diff --git a/ee/packages/license/src/token.ts b/ee/packages/license/src/token.ts index 09ad5a940b58..9a77adbc4418 100644 --- a/ee/packages/license/src/token.ts +++ b/ee/packages/license/src/token.ts @@ -3,13 +3,36 @@ import crypto from 'crypto'; import type { ILicenseV3 } from '@rocket.chat/core-typings'; import { verify, sign, getPairs } from '@rocket.chat/jwt'; +const base64Decode = (key: string): string => Buffer.from(key, 'base64').toString('utf-8'); + const PUBLIC_LICENSE_KEY_V2 = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFxV1Nza2Q5LzZ6Ung4a3lQY2ljcwpiMzJ3Mnd4VnV3N3lCVDk2clEvOEQreU1lQ01POXdTU3BIYS85bkZ5d293RXRpZ3B0L3dyb1BOK1ZHU3didHdQCkZYQmVxRWxCbmRHRkFsODZlNStFbGlIOEt6L2hHbkNtSk5tWHB4RUsyUkUwM1g0SXhzWVg3RERCN010eC9pcXMKY2pCL091dlNCa2ppU2xlUzdibE5JVC9kQTdLNC9DSjNvaXUwMmJMNEV4Y2xDSGVwenFOTWVQM3dVWmdweE9uZgpOT3VkOElYWUs3M3pTY3VFOEUxNTdZd3B6Q0twVmFIWDdaSmY4UXVOc09PNVcvYUlqS2wzTDYyNjkrZUlPRXJHCndPTm1hSG56Zmc5RkxwSmh6Z3BPMzhhVm43NnZENUtLakJhaldza1krNGEyZ1NRbUtOZUZxYXFPb3p5RUZNMGUKY0ZXWlZWWjNMZWg0dkVNb1lWUHlJeng5Nng4ZjIveW1QbmhJdXZRdjV3TjRmeWVwYTdFWTVVQ2NwNzF6OGtmUAo0RmNVelBBMElEV3lNaWhYUi9HNlhnUVFaNEdiL3FCQmh2cnZpSkNGemZZRGNKZ0w3RmVnRllIUDNQR0wwN1FnCnZMZXZNSytpUVpQcnhyYnh5U3FkUE9rZ3VyS2pWclhUVXI0QTlUZ2lMeUlYNVVsSnEzRS9SVjdtZk9xWm5MVGEKU0NWWEhCaHVQbG5DR1pSMDFUb1RDZktoTUcxdTBDRm5MMisxNWhDOWZxT21XdjlRa2U0M3FsSjBQZ0YzVkovWAp1eC9tVHBuazlnbmJHOUpIK21mSDM5Um9GdlROaW5Zd1NNdll6dXRWT242OXNPemR3aERsYTkwbDNBQ2g0eENWCks3Sk9YK3VIa29OdTNnMmlWeGlaVU0wQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo='; +const PUBLIC_STATS_KEY_V2 = + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFuT0IvR3lCUXg1cVJZOC83dGxhTApkd0hSOWZBWHVzQ3ZBVU9YRjFPYjExaWx4ejdqY0pqWitaRjE2UTk3bjF3UDlvQnJMUDg5M3ZXUlc1bUtDdFpDClJQNTdJU1o2YjlHOXoyNStnV0NEa3ZZUUg1djJlMGoxUnE5TnNYMktlTWViOUZXenRQVFEvdDRvUW1SdUpiTEIKV013ajRRbHZ4OStpdWpkdGdQYmx5S0VFM0I3T1RPWk8xNzNOK2RDZW8wOWlQcStyKzBiRjNGSTRVcVFiZkFrdApOdGd0OUxVbjdlalNaQXNhSTZPbVBmOVVvSzM4NUdxWUxvbk1WbXBFbmZyRDlHSHl0OXFOY2pCTmZ1MUJLZWlUClBwVit6WE1FViszLzZ2bi84OW1id28zS0ZtTnRxVTJpRk5Yak5Cb2w1ZE82N3VlM0pURXBVQmJWRHZ2V3gwTk8KWndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='; -const PUBLIC_LICENSE_KEY_V3 = Buffer.from(PUBLIC_LICENSE_KEY_V2, 'base64').toString('utf-8'); +const PUBLIC_LICENSE_KEY_V3 = base64Decode(PUBLIC_LICENSE_KEY_V2); let TEST_KEYS: [string, string] | undefined = undefined; +export async function decryptStatsToken(encrypted: string): Promise { + if (process.env.NODE_ENV?.toLowerCase() === 'test') { + TEST_KEYS = TEST_KEYS ?? (await getPairs()); + + if (!TEST_KEYS) { + throw new Error('Missing PUBLIC_STATS_KEY_V3'); + } + + const [spki] = TEST_KEYS; + + const [payload] = await verify(encrypted, spki); + return JSON.stringify(payload); + } + + const [payload] = await verify(encrypted, base64Decode(PUBLIC_STATS_KEY_V2)); + + return JSON.stringify(payload); +} + export async function decrypt(encrypted: string): Promise { if (process.env.NODE_ENV === 'test') { if (encrypted.startsWith('RCV3_')) { @@ -52,3 +75,15 @@ export async function encrypt(license: ILicenseV3): Promise { return `RCV3_${await sign(license, pkcs8)}`; } + +export async function encryptStatsToken(data: Record): Promise { + if (process.env.NODE_ENV !== 'test') { + throw new Error('This function should only be used in tests'); + } + + TEST_KEYS = TEST_KEYS ?? (await getPairs()); + + const [, pkcs8] = TEST_KEYS; + + return sign(data, pkcs8); +} diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index e199e8a1dcb6..de4cfac7193f 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -402,6 +402,9 @@ "Agents": "Agents", "Agree": "Agree", "AI_Actions": "AI Actions", + "AirGapped_Restriction_Warning": "**Your air-gapped workspace will enter read-only mode in {{remainingDays}} days.** \n Users will still be able to access rooms and read existing messages but will be unable to send new messages. \n Reconnect it to the internet or [upgrade to a premium license](https://go.rocket.chat/i/air-gapped) to prevent this.", + "Airgapped_workspace_warning": "This air-gapped workspace will enter read-only mode in {{remainingDays}} days. <2>Connect it to internet or upgrade to a premium plan to prevent this.", + "Airgapped_workspace_restriction": "This air-gapped workspace is in read-only mode. <2>Connect it to internet or upgraded to a premium plan to restore full functionality.", "Alerts": "Alerts", "Alias": "Alias", "Alias_Format": "Alias Format", @@ -1149,6 +1152,7 @@ "Contextualbar_resizable_description": "Adjust the size of the contextual bar by clicking and dragging the edge, giving you instant customization and flexibility.", "Free_Edition": "Free edition", "Composer_not_available_phone_calls": "Messages are not available on phone calls", + "Composer_readonly_airgapped": "<0>Workspace in read-only mode. Admins can restore full functionality by connecting it to internet or upgrading to a premium plan.", "Condensed": "Condensed", "Condition": "Condition", "Commit_details": "Commit Details", diff --git a/packages/mock-providers/src/MockedAppRootBuilder.tsx b/packages/mock-providers/src/MockedAppRootBuilder.tsx index 36fe123c244a..69ca9cde99e1 100644 --- a/packages/mock-providers/src/MockedAppRootBuilder.tsx +++ b/packages/mock-providers/src/MockedAppRootBuilder.tsx @@ -239,7 +239,7 @@ export class MockedAppRootBuilder { return this; } - withJohnDoe(): this { + withJohnDoe(overrides: Partial = {}): this { this.user.userId = 'john.doe'; this.user.user = { @@ -251,6 +251,7 @@ export class MockedAppRootBuilder { _updatedAt: new Date(), roles: ['admin'], type: 'user', + ...overrides, }; return this; diff --git a/packages/model-typings/src/models/IStatisticsModel.ts b/packages/model-typings/src/models/IStatisticsModel.ts index fe4534eaee0f..4926c59b7135 100644 --- a/packages/model-typings/src/models/IStatisticsModel.ts +++ b/packages/model-typings/src/models/IStatisticsModel.ts @@ -5,4 +5,5 @@ import type { IBaseModel } from './IBaseModel'; export interface IStatisticsModel extends IBaseModel { findLast(): Promise; findMonthlyPeakConnections(): Promise | null>; + findLastStatsToken(): Promise; } diff --git a/packages/ui-composer/src/MessageFooterCallout/MessageFooterCallout.tsx b/packages/ui-composer/src/MessageFooterCallout/MessageFooterCallout.tsx index 5835ad683186..9c946f9fadd1 100644 --- a/packages/ui-composer/src/MessageFooterCallout/MessageFooterCallout.tsx +++ b/packages/ui-composer/src/MessageFooterCallout/MessageFooterCallout.tsx @@ -27,6 +27,7 @@ const MessageFooterCallout = forwardRef< alignItems='center' minHeight='x48' justifyContent='center' + color='default' {...props} /> ); From f4365b7dd45ab3f3e42de703ff76584b069f5a66 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Fri, 18 Oct 2024 21:54:33 -0300 Subject: [PATCH 02/12] chore!: remove query field on custom emojis listing (#33650) --- .changeset/perfect-wolves-impress.md | 6 +++ .../app/api/server/helpers/parseJsonQuery.ts | 1 + apps/meteor/app/api/server/v1/emoji-custom.ts | 42 +++++++++++++++---- .../customEmoji/EditCustomEmojiWithData.tsx | 2 +- .../tests/end-to-end/api/emoji-custom.ts | 4 +- packages/rest-typings/src/v1/emojiCustom.ts | 13 +++--- 6 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 .changeset/perfect-wolves-impress.md diff --git a/.changeset/perfect-wolves-impress.md b/.changeset/perfect-wolves-impress.md new file mode 100644 index 000000000000..becaf1d706d2 --- /dev/null +++ b/.changeset/perfect-wolves-impress.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': major +'@rocket.chat/meteor': major +--- + +Changes custom emoji listing endpoint by moving query params from the 'query' attribute to standard query parameters. diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts index bbd58e118556..35fe6155a396 100644 --- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts +++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts @@ -62,6 +62,7 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ '/api/v1/custom-sounds.list', '/api/v1/channels.list', '/api/v1/channels.online', + '/api/v1/emoji-custom.list', ].includes(route); const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE'; diff --git a/apps/meteor/app/api/server/v1/emoji-custom.ts b/apps/meteor/app/api/server/v1/emoji-custom.ts index 9cbf202896e1..261743e6c0a9 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.ts +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -1,5 +1,6 @@ import { Media } from '@rocket.chat/core-services'; import { EmojiCustom } from '@rocket.chat/models'; +import { isEmojiCustomList } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -11,22 +12,41 @@ import { getPaginationItems } from '../helpers/getPaginationItems'; import { findEmojisCustom } from '../lib/emoji-custom'; import { getUploadFormData } from '../lib/getUploadFormData'; +function validateDateParam(paramName: string, paramValue: string | undefined): Date | undefined { + if (!paramValue) { + return undefined; + } + + const date = new Date(paramValue); + if (isNaN(date.getTime())) { + throw new Meteor.Error('error-roomId-param-invalid', `The "${paramName}" query parameter must be a valid date.`); + } + + return date; +} + API.v1.addRoute( 'emoji-custom.list', - { authRequired: true }, + { authRequired: true, validateParams: isEmojiCustomList }, { async get() { const { query } = await this.parseJsonQuery(); - const { updatedSince } = this.queryParams; - if (updatedSince) { - const updatedSinceDate = new Date(updatedSince); - if (isNaN(Date.parse(updatedSince))) { - throw new Meteor.Error('error-roomId-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); - } + const { updatedSince, _updatedAt, _id } = this.queryParams; + + const updatedSinceDate = validateDateParam('updatedSince', updatedSince); + const _updatedAtDate = validateDateParam('_updatedAt', _updatedAt); + + if (updatedSinceDate) { const [update, remove] = await Promise.all([ - EmojiCustom.find({ ...query, _updatedAt: { $gt: updatedSinceDate } }).toArray(), + EmojiCustom.find({ + ...query, + ...(_id ? { _id } : {}), + ...(_updatedAtDate ? { _updatedAt: { $gt: _updatedAtDate } } : {}), + _updatedAt: { $gt: updatedSinceDate }, + }).toArray(), EmojiCustom.trashFindDeletedAfter(updatedSinceDate).toArray(), ]); + return API.v1.success({ emojis: { update, @@ -37,7 +57,11 @@ API.v1.addRoute( return API.v1.success({ emojis: { - update: await EmojiCustom.find(query).toArray(), + update: await EmojiCustom.find({ + ...query, + ...(_id ? { _id } : {}), + ...(_updatedAtDate ? { _updatedAt: { $gt: _updatedAtDate } } : {}), + }).toArray(), remove: [], }, }); diff --git a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx index 2ee8a79f5568..fff37214b530 100644 --- a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx +++ b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx @@ -14,7 +14,7 @@ type EditCustomEmojiWithDataProps = { const EditCustomEmojiWithData = ({ _id, onChange, close, ...props }: EditCustomEmojiWithDataProps) => { const t = useTranslation(); - const query = useMemo(() => ({ query: JSON.stringify({ _id }) }), [_id]); + const query = useMemo(() => ({ _id }), [_id]); const getEmojis = useEndpoint('GET', '/v1/emoji-custom.list'); diff --git a/apps/meteor/tests/end-to-end/api/emoji-custom.ts b/apps/meteor/tests/end-to-end/api/emoji-custom.ts index 31042363836e..c87a83783e0e 100644 --- a/apps/meteor/tests/end-to-end/api/emoji-custom.ts +++ b/apps/meteor/tests/end-to-end/api/emoji-custom.ts @@ -214,7 +214,7 @@ describe('[EmojiCustom]', () => { it('should return emojis when use "query" query parameter', (done) => { void request .get(api('emoji-custom.list')) - .query({ query: `{ "_updatedAt": { "$gt": { "$date": "${new Date().toISOString()}" } } }` }) + .query({ _updatedAt: new Date().toISOString() }) .set(credentials) .expect(200) .expect((res) => { @@ -242,7 +242,7 @@ describe('[EmojiCustom]', () => { it('should return emojis when use both, "updateSince" and "query" query parameter', (done) => { void request .get(api('emoji-custom.list')) - .query({ query: `{"_updatedAt": {"$gt": { "$date": "${new Date().toISOString()}" } }}`, updatedSince: new Date().toISOString() }) + .query({ _updatedAt: new Date().toISOString(), updatedSince: new Date().toISOString() }) .set(credentials) .expect(200) .expect((res) => { diff --git a/packages/rest-typings/src/v1/emojiCustom.ts b/packages/rest-typings/src/v1/emojiCustom.ts index e0cd87906c36..1ffb41f671a8 100644 --- a/packages/rest-typings/src/v1/emojiCustom.ts +++ b/packages/rest-typings/src/v1/emojiCustom.ts @@ -25,10 +25,7 @@ const emojiCustomDeletePropsSchema = { export const isEmojiCustomDelete = ajv.compile(emojiCustomDeletePropsSchema); -type emojiCustomList = { - query: string; - updatedSince?: string; -}; +type emojiCustomList = { query?: string; updatedSince?: string; _updatedAt?: string; _id?: string }; const emojiCustomListSchema = { type: 'object', @@ -40,8 +37,14 @@ const emojiCustomListSchema = { type: 'string', nullable: true, }, + _updatedAt: { + type: 'string', + }, + _id: { + type: 'string', + }, }, - required: ['query'], + required: [], additionalProperties: false, }; From 429d00024c87ae44a14a1bab8ebaf8febfd1faec Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 18 Oct 2024 22:03:47 -0300 Subject: [PATCH 03/12] feat: Trace api calls (#32779) Co-authored-by: Diego Sampaio Co-authored-by: Ricardo Garim Co-authored-by: Guilherme Gazzo --- .gitignore | 1 + apps/meteor/app/api/server/api.ts | 26 +- apps/meteor/app/lib/server/lib/debug.js | 18 +- .../app/metrics/server/lib/collectMetrics.ts | 16 +- .../server/NotificationQueue.ts | 26 +- .../server/functions/sendUsageReport.ts | 43 +- apps/meteor/package.json | 4 + .../rocketchat-mongo-config/server/index.js | 3 + apps/meteor/server/database/utils.ts | 3 + apps/meteor/server/main.ts | 1 + apps/meteor/server/models/raw/BaseRaw.ts | 3 + apps/meteor/server/tracing.ts | 3 + development/agent.yml | 16 + development/collector.config.yml | 24 + development/docker-compose-monitoring.yml | 79 +++ development/grafana-datasources.yml | 30 + development/prometheus.yml | 11 + development/tempo.yml | 60 ++ ee/apps/account-service/Dockerfile | 3 + ee/apps/account-service/package.json | 1 + ee/apps/account-service/src/service.ts | 3 + ee/apps/authorization-service/Dockerfile | 3 + ee/apps/authorization-service/package.json | 1 + ee/apps/authorization-service/src/service.ts | 3 + ee/apps/ddp-streamer/Dockerfile | 3 + ee/apps/ddp-streamer/package.json | 1 + ee/apps/ddp-streamer/src/service.ts | 3 + ee/apps/omnichannel-transcript/Dockerfile | 3 + ee/apps/omnichannel-transcript/package.json | 1 + ee/apps/omnichannel-transcript/src/service.ts | 3 + ee/apps/presence-service/Dockerfile | 3 + ee/apps/presence-service/package.json | 1 + ee/apps/presence-service/src/service.ts | 3 + ee/apps/queue-worker/Dockerfile | 3 + ee/apps/queue-worker/package.json | 1 + ee/apps/queue-worker/src/service.ts | 3 + ee/apps/stream-hub-service/Dockerfile | 3 + ee/apps/stream-hub-service/package.json | 1 + ee/apps/stream-hub-service/src/service.ts | 3 + .../network-broker/src/NetworkBroker.ts | 39 +- ee/packages/network-broker/src/index.ts | 22 - packages/core-services/package.json | 1 + packages/core-services/src/index.ts | 1 + .../src/lib/asyncMethodCallContext.ts | 44 ++ packages/core-services/src/lib/mongo.ts | 8 +- packages/instance-status/package.json | 3 +- packages/instance-status/src/index.ts | 5 +- packages/tracing/.eslintrc.json | 4 + packages/tracing/package.json | 32 + packages/tracing/src/index.ts | 96 +++ packages/tracing/src/traceDatabaseCalls.ts | 65 ++ packages/tracing/tsconfig.json | 9 + tsconfig.base.server.json | 5 +- yarn.lock | 581 +++++++++++++++++- 54 files changed, 1243 insertions(+), 88 deletions(-) create mode 100644 apps/meteor/server/tracing.ts create mode 100644 development/agent.yml create mode 100644 development/collector.config.yml create mode 100644 development/docker-compose-monitoring.yml create mode 100644 development/grafana-datasources.yml create mode 100644 development/prometheus.yml create mode 100644 development/tempo.yml create mode 100644 packages/core-services/src/lib/asyncMethodCallContext.ts create mode 100644 packages/tracing/.eslintrc.json create mode 100644 packages/tracing/package.json create mode 100644 packages/tracing/src/index.ts create mode 100644 packages/tracing/src/traceDatabaseCalls.ts create mode 100644 packages/tracing/tsconfig.json diff --git a/.gitignore b/.gitignore index 8ca2d018f92e..8741b33f36c3 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ data/ registration.yaml storybook-static +development/tempo-data/ diff --git a/apps/meteor/app/api/server/api.ts b/apps/meteor/app/api/server/api.ts index 76f9cc44de7a..7bb7f5af28ba 100644 --- a/apps/meteor/app/api/server/api.ts +++ b/apps/meteor/app/api/server/api.ts @@ -3,6 +3,7 @@ import { Logger } from '@rocket.chat/logger'; import { Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import type { JoinPathPattern, Method } from '@rocket.chat/rest-typings'; +import { tracerSpan } from '@rocket.chat/tracing'; import { Accounts } from 'meteor/accounts-base'; import { DDP } from 'meteor/ddp'; import { DDPCommon } from 'meteor/ddp-common'; @@ -645,8 +646,29 @@ export class APIClass extends Restivus { this.queryFields = options.queryFields; this.parseJsonQuery = api.parseJsonQuery.bind(this as PartialThis); - result = - (await DDP._CurrentInvocation.withValue(invocation as any, async () => originalAction.apply(this))) || API.v1.success(); + result = await tracerSpan( + `${this.request.method} ${this.request.url}`, + { + attributes: { + url: this.request.url, + route: this.request.route, + method: this.request.method, + userId: this.userId, + }, + }, + async (span) => { + if (span) { + this.response.setHeader('X-Trace-Id', span.spanContext().traceId); + } + + const result = + (await DDP._CurrentInvocation.withValue(invocation as any, async () => originalAction.apply(this))) || API.v1.success(); + + span?.setAttribute('status', result.statusCode); + + return result; + }, + ); log.http({ status: result.statusCode, diff --git a/apps/meteor/app/lib/server/lib/debug.js b/apps/meteor/app/lib/server/lib/debug.js index cbf38528579f..71daf1a4fbcf 100644 --- a/apps/meteor/app/lib/server/lib/debug.js +++ b/apps/meteor/app/lib/server/lib/debug.js @@ -1,5 +1,6 @@ import { InstanceStatus } from '@rocket.chat/instance-status'; import { Logger } from '@rocket.chat/logger'; +import { tracerActiveSpan } from '@rocket.chat/tracing'; import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; import _ from 'underscore'; @@ -72,9 +73,20 @@ const wrapMethods = function (name, originalHandler, methodsMap) { ...getMethodArgs(name, originalArgs), }); - const result = originalHandler.apply(this, originalArgs); - end(); - return result; + return tracerActiveSpan( + `Method ${name}`, + { + attributes: { + method: name, + userId: this.userId, + }, + }, + async () => { + const result = await originalHandler.apply(this, originalArgs); + end(); + return result; + }, + ); }; }; diff --git a/apps/meteor/app/metrics/server/lib/collectMetrics.ts b/apps/meteor/app/metrics/server/lib/collectMetrics.ts index 978b3d59ec98..8f2b256f6759 100644 --- a/apps/meteor/app/metrics/server/lib/collectMetrics.ts +++ b/apps/meteor/app/metrics/server/lib/collectMetrics.ts @@ -1,6 +1,7 @@ import http from 'http'; import { Statistics } from '@rocket.chat/models'; +import { tracerSpan } from '@rocket.chat/tracing'; import connect from 'connect'; import { Facts } from 'meteor/facts-base'; import { Meteor } from 'meteor/meteor'; @@ -169,7 +170,20 @@ const updatePrometheusConfig = async (): Promise => { host: process.env.BIND_IP || '0.0.0.0', }); - timer = setInterval(setPrometheusData, 5000); + timer = setInterval(async () => { + void tracerSpan( + 'setPrometheusData', + { + attributes: { + port: is.port, + host: process.env.BIND_IP || '0.0.0.0', + }, + }, + () => { + void setPrometheusData(); + }, + ); + }, 5000); } clearInterval(resetTimer); diff --git a/apps/meteor/app/notification-queue/server/NotificationQueue.ts b/apps/meteor/app/notification-queue/server/NotificationQueue.ts index f58eaec3f576..78a9e931fcb5 100644 --- a/apps/meteor/app/notification-queue/server/NotificationQueue.ts +++ b/apps/meteor/app/notification-queue/server/NotificationQueue.ts @@ -1,5 +1,6 @@ import type { INotification, INotificationItemPush, INotificationItemEmail, NotificationItem, IUser } from '@rocket.chat/core-typings'; import { NotificationQueue, Users } from '@rocket.chat/models'; +import { tracerSpan } from '@rocket.chat/tracing'; import { Meteor } from 'meteor/meteor'; import { SystemLogger } from '../../../server/lib/logger/system'; @@ -43,7 +44,19 @@ class NotificationClass { setTimeout(async () => { try { - await this.worker(); + const continueLater = await tracerSpan( + 'NotificationWorker', + { + attributes: { + workerTime: new Date().toISOString(), + }, + }, + () => this.worker(), + ); + + if (continueLater) { + this.executeWorkerLater(); + } } catch (err) { SystemLogger.error({ msg: 'Error sending notification', err }); this.executeWorkerLater(); @@ -51,17 +64,17 @@ class NotificationClass { }, this.cyclePause); } - async worker(counter = 0): Promise { + async worker(counter = 0): Promise { const notification = await this.getNextNotification(); if (!notification) { - return this.executeWorkerLater(); + return true; } // Once we start notifying the user we anticipate all the schedules const flush = await NotificationQueue.clearScheduleByUserId(notification.uid); - // start worker again it queue flushed + // start worker again if queue flushed if (flush.modifiedCount) { await NotificationQueue.unsetSendingById(notification._id); return this.worker(counter); @@ -86,9 +99,10 @@ class NotificationClass { } if (counter >= this.maxBatchSize) { - return this.executeWorkerLater(); + return true; } - await this.worker(counter++); + + return this.worker(counter++); } getNextNotification(): Promise { diff --git a/apps/meteor/app/statistics/server/functions/sendUsageReport.ts b/apps/meteor/app/statistics/server/functions/sendUsageReport.ts index dc684fe5fa6a..a4b1e6182c96 100644 --- a/apps/meteor/app/statistics/server/functions/sendUsageReport.ts +++ b/apps/meteor/app/statistics/server/functions/sendUsageReport.ts @@ -1,35 +1,38 @@ import type { Logger } from '@rocket.chat/logger'; import { Statistics } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { tracerSpan } from '@rocket.chat/tracing'; import { Meteor } from 'meteor/meteor'; import { statistics } from '..'; import { getWorkspaceAccessToken } from '../../../cloud/server'; export async function sendUsageReport(logger: Logger): Promise { - const cronStatistics = await statistics.save(); + return tracerSpan('generateStatistics', {}, async () => { + const cronStatistics = await statistics.save(); - try { - const token = await getWorkspaceAccessToken(); - const headers = { ...(token && { Authorization: `Bearer ${token}` }) }; + try { + const token = await getWorkspaceAccessToken(); + const headers = { ...(token && { Authorization: `Bearer ${token}` }) }; - const response = await fetch('https://collector.rocket.chat/', { - method: 'POST', - body: { - ...cronStatistics, - host: Meteor.absoluteUrl(), - }, - headers, - }); + const response = await fetch('https://collector.rocket.chat/', { + method: 'POST', + body: { + ...cronStatistics, + host: Meteor.absoluteUrl(), + }, + headers, + }); - const { statsToken } = await response.json(); + const { statsToken } = await response.json(); - if (statsToken != null) { - await Statistics.updateOne({ _id: cronStatistics._id }, { $set: { statsToken } }); - return statsToken; + if (statsToken != null) { + await Statistics.updateOne({ _id: cronStatistics._id }, { $set: { statsToken } }); + return statsToken; + } + } catch (error) { + /* error*/ + logger.warn('Failed to send usage report'); } - } catch (error) { - /* error*/ - logger.warn('Failed to send usage report'); - } + }); } diff --git a/apps/meteor/package.json b/apps/meteor/package.json index fafecd90266b..107688676fe9 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -225,6 +225,9 @@ "@nivo/heatmap": "0.84.0", "@nivo/line": "0.84.0", "@nivo/pie": "0.84.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.53.0", + "@opentelemetry/sdk-node": "^0.53.0", "@react-aria/color": "^3.0.0-beta.15", "@react-aria/toolbar": "^3.0.0-beta.1", "@react-pdf/renderer": "^3.4.5", @@ -278,6 +281,7 @@ "@rocket.chat/sha256": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tools": "workspace:^", + "@rocket.chat/tracing": "workspace:^", "@rocket.chat/ui-avatar": "workspace:^", "@rocket.chat/ui-client": "workspace:^", "@rocket.chat/ui-composer": "workspace:^", diff --git a/apps/meteor/packages/rocketchat-mongo-config/server/index.js b/apps/meteor/packages/rocketchat-mongo-config/server/index.js index 684620d09054..0cf40df6bbb4 100644 --- a/apps/meteor/packages/rocketchat-mongo-config/server/index.js +++ b/apps/meteor/packages/rocketchat-mongo-config/server/index.js @@ -20,6 +20,9 @@ const mongoConnectionOptions = { // add retryWrites=false if not present in MONGO_URL ...(!process.env.MONGO_URL.includes('retryWrites') && { retryWrites: false }), // ignoreUndefined: false, // TODO evaluate adding this config + + // TODO ideally we should call isTracingEnabled(), but since this is a Meteor package we can't :/ + monitorCommands: ['yes', 'true'].includes(String(process.env.TRACING_ENABLED).toLowerCase()), }; const mongoOptionStr = process.env.MONGO_OPTIONS; diff --git a/apps/meteor/server/database/utils.ts b/apps/meteor/server/database/utils.ts index ec3864586924..e07df1e9e737 100644 --- a/apps/meteor/server/database/utils.ts +++ b/apps/meteor/server/database/utils.ts @@ -1,3 +1,6 @@ +import { initDatabaseTracing } from '@rocket.chat/tracing'; import { MongoInternals } from 'meteor/mongo'; export const { db, client } = MongoInternals.defaultRemoteCollectionDriver().mongo; + +initDatabaseTracing(client); diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index 294cdcd4feab..b5f34876c0bd 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -1,3 +1,4 @@ +import './tracing'; import './models/startup'; /** * ./settings uses top level await, in theory the settings creation diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts index 8e1bd302b50d..0a7fc56449b0 100644 --- a/apps/meteor/server/models/raw/BaseRaw.ts +++ b/apps/meteor/server/models/raw/BaseRaw.ts @@ -1,3 +1,4 @@ +import { traceInstanceMethods } from '@rocket.chat/core-services'; import type { RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { IBaseModel, DefaultFields, ResultFields, FindPaginated, InsertionModel } from '@rocket.chat/model-typings'; import type { Updater } from '@rocket.chat/models'; @@ -76,6 +77,8 @@ export abstract class BaseRaw< void this.createIndexes(); this.preventSetUpdatedAt = options?.preventSetUpdatedAt ?? false; + + return traceInstanceMethods(this); } private pendingIndexes: Promise | undefined; diff --git a/apps/meteor/server/tracing.ts b/apps/meteor/server/tracing.ts new file mode 100644 index 000000000000..e08bfebb20ac --- /dev/null +++ b/apps/meteor/server/tracing.ts @@ -0,0 +1,3 @@ +import { startTracing } from '@rocket.chat/tracing'; + +startTracing({ service: 'core' }); diff --git a/development/agent.yml b/development/agent.yml new file mode 100644 index 000000000000..e40e39401d64 --- /dev/null +++ b/development/agent.yml @@ -0,0 +1,16 @@ +server: + log_level: debug + +traces: + configs: + - name: default + receivers: + otlp: + protocols: + grpc: + remote_write: + - endpoint: tempo:4317 + insecure: true + batch: + timeout: 5s + send_batch_size: 100 diff --git a/development/collector.config.yml b/development/collector.config.yml new file mode 100644 index 000000000000..a284c46de807 --- /dev/null +++ b/development/collector.config.yml @@ -0,0 +1,24 @@ +receivers: + otlp: + protocols: + grpc: + http: + +processors: + batch: + timeout: 100ms + +exporters: + logging: + loglevel: debug + otlp/1: + endpoint: tempo:4317 + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp/1] diff --git a/development/docker-compose-monitoring.yml b/development/docker-compose-monitoring.yml new file mode 100644 index 000000000000..90c75e67db22 --- /dev/null +++ b/development/docker-compose-monitoring.yml @@ -0,0 +1,79 @@ +services: + # Tempo runs as user 10001, and docker compose creates the volume as root. + # As such, we need to chown the volume in order for Tempo to start correctly. + init: + image: &tempoImage grafana/tempo:latest + user: root + entrypoint: + - "chown" + - "10001:10001" + - "/var/tempo" + volumes: + - ./tempo-data:/var/tempo + + tempo: + image: *tempoImage + command: [ "-config.file=/etc/tempo.yaml" ] + volumes: + - ./tempo.yml:/etc/tempo.yaml + - ./tempo-data:/var/tempo + ports: + - "14268" # jaeger ingest + - "3200" # tempo + - "4317" # otlp grpc + - "4318" # otlp http + - "9411" # zipkin2024-04-23T16:16:57+0000 + depends_on: + - init + + # # Generate fake traces... + # k6-tracing: + # image: ghcr.io/grafana/xk6-client-tracing:v0.0.5 + # environment: + # - ENDPOINT=agent:4317 + # restart: always + # depends_on: + # - tempo + + otel-collector: + image: otel/opentelemetry-collector-contrib:0.100.0 + command: + - "--config" + - "/otel-local-config.yaml" + volumes: + - ./collector.config.yml:/otel-local-config.yaml + ports: + - "4317:4317" + + # And put them in a Grafana Agent pipeline... + agent: + image: grafana/agent:v0.27.1 + volumes: + - ./agent.yml:/etc/agent.yaml + entrypoint: + - /bin/agent + - -config.file=/etc/agent.yaml + + prometheus: + image: prom/prometheus:latest + command: + - --config.file=/etc/prometheus.yaml + - --web.enable-remote-write-receiver + - --enable-feature=exemplar-storage + - --enable-feature=native-histograms + volumes: + - ./prometheus.yml:/etc/prometheus.yaml + ports: + - "9090:9090" + + grafana: + image: grafana/grafana:11.0.0 + volumes: + - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yaml + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor + ports: + - "4001:3000" diff --git a/development/grafana-datasources.yml b/development/grafana-datasources.yml new file mode 100644 index 000000000000..4a3bc2c4e742 --- /dev/null +++ b/development/grafana-datasources.yml @@ -0,0 +1,30 @@ +apiVersion: 1 + +datasources: +- name: Prometheus + type: prometheus + uid: prometheus + access: proxy + orgId: 1 + url: http://prometheus:9090 + basicAuth: false + isDefault: false + version: 1 + editable: false + jsonData: + httpMethod: GET +- name: Tempo + type: tempo + access: proxy + orgId: 1 + url: http://tempo:3200 + basicAuth: false + isDefault: true + version: 1 + editable: false + apiVersion: 1 + uid: tempo + jsonData: + httpMethod: GET + serviceMap: + datasourceUid: prometheus diff --git a/development/prometheus.yml b/development/prometheus.yml new file mode 100644 index 000000000000..eda5d0261c8f --- /dev/null +++ b/development/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: [ 'localhost:9090' ] + - job_name: 'tempo' + static_configs: + - targets: [ 'tempo:3200' ] diff --git a/development/tempo.yml b/development/tempo.yml new file mode 100644 index 000000000000..b1ab5ce7607b --- /dev/null +++ b/development/tempo.yml @@ -0,0 +1,60 @@ +stream_over_http_enabled: true +server: + http_listen_port: 3200 + log_level: info + +query_frontend: + search: + duration_slo: 5s + throughput_bytes_slo: 1.073741824e+09 + trace_by_id: + duration_slo: 5s + +distributor: + receivers: # this configuration will listen on all ports and protocols that tempo is capable of. + jaeger: # the receives all come from the OpenTelemetry collector. more configuration information can + protocols: # be found there: https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver + thrift_http: # + grpc: # for a production deployment you should only enable the receivers you need! + thrift_binary: + thrift_compact: + zipkin: + otlp: + protocols: + http: + grpc: + opencensus: + +ingester: + max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally + +compactor: + compaction: + block_retention: 1h # overall Tempo trace retention. set for demo purposes + +metrics_generator: + registry: + external_labels: + source: tempo + cluster: docker-compose + storage: + path: /var/tempo/generator/wal + remote_write: + - url: http://prometheus:9090/api/v1/write + send_exemplars: true + traces_storage: + path: /var/tempo/generator/traces + +storage: + trace: + backend: local # backend configuration to use + wal: + path: /var/tempo/wal # where to store the wal locally + local: + path: /var/tempo/blocks + +overrides: + defaults: + metrics_generator: + processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator + generate_native_histograms: both diff --git a/ee/apps/account-service/Dockerfile b/ee/apps/account-service/Dockerfile index dec9433667f4..7c7f4f5ebb3d 100644 --- a/ee/apps/account-service/Dockerfile +++ b/ee/apps/account-service/Dockerfile @@ -47,6 +47,9 @@ COPY ./ee/packages/network-broker/dist ee/packages/network-broker/dist COPY ./ee/packages/license/package.json packages/license/package.json COPY ./ee/packages/license/dist packages/license/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json index 78326b6065f7..c88c6292957f 100644 --- a/ee/apps/account-service/package.json +++ b/ee/apps/account-service/package.json @@ -24,6 +24,7 @@ "@rocket.chat/rest-typings": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tools": "workspace:^", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "bcrypt": "^5.0.1", "ejson": "^2.2.3", diff --git a/ee/apps/account-service/src/service.ts b/ee/apps/account-service/src/service.ts index c2f64e37bde3..b5279998a52b 100755 --- a/ee/apps/account-service/src/service.ts +++ b/ee/apps/account-service/src/service.ts @@ -1,9 +1,12 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; +startTracing({ service: 'account-service' }); + const PORT = process.env.PORT || 3033; (async () => { diff --git a/ee/apps/authorization-service/Dockerfile b/ee/apps/authorization-service/Dockerfile index 612dbe5d73c9..792afc778088 100644 --- a/ee/apps/authorization-service/Dockerfile +++ b/ee/apps/authorization-service/Dockerfile @@ -47,6 +47,9 @@ COPY ./ee/packages/network-broker/dist ee/packages/network-broker/dist COPY ./ee/packages/license/package.json packages/license/package.json COPY ./ee/packages/license/dist packages/license/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json index 3ce8f9734916..1e1aae2fa34e 100644 --- a/ee/apps/authorization-service/package.json +++ b/ee/apps/authorization-service/package.json @@ -23,6 +23,7 @@ "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/rest-typings": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", diff --git a/ee/apps/authorization-service/src/service.ts b/ee/apps/authorization-service/src/service.ts index 1698ef7a115c..699de7aeb518 100755 --- a/ee/apps/authorization-service/src/service.ts +++ b/ee/apps/authorization-service/src/service.ts @@ -1,11 +1,14 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; const PORT = process.env.PORT || 3034; +startTracing({ service: 'authorization-service' }); + (async () => { const db = await getConnection(); diff --git a/ee/apps/ddp-streamer/Dockerfile b/ee/apps/ddp-streamer/Dockerfile index 40dcfa5ccb3d..e2106da66a5b 100644 --- a/ee/apps/ddp-streamer/Dockerfile +++ b/ee/apps/ddp-streamer/Dockerfile @@ -53,6 +53,9 @@ COPY ./ee/packages/license/dist packages/license/dist COPY ./packages/instance-status/package.json packages/instance-status/package.json COPY ./packages/instance-status/dist packages/instance-status/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json index edfbb38e5cbf..e2ee7251513d 100644 --- a/ee/apps/ddp-streamer/package.json +++ b/ee/apps/ddp-streamer/package.json @@ -25,6 +25,7 @@ "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/rest-typings": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", + "@rocket.chat/tracing": "workspace:^", "colorette": "^1.4.0", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", diff --git a/ee/apps/ddp-streamer/src/service.ts b/ee/apps/ddp-streamer/src/service.ts index 58552240cadd..4b43e1baed96 100755 --- a/ee/apps/ddp-streamer/src/service.ts +++ b/ee/apps/ddp-streamer/src/service.ts @@ -1,8 +1,11 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; +startTracing({ service: 'ddp-streamer' }); + (async () => { const db = await getConnection(); diff --git a/ee/apps/omnichannel-transcript/Dockerfile b/ee/apps/omnichannel-transcript/Dockerfile index 7c3e0eaaa070..b2595b06f1fe 100644 --- a/ee/apps/omnichannel-transcript/Dockerfile +++ b/ee/apps/omnichannel-transcript/Dockerfile @@ -56,6 +56,9 @@ COPY ./ee/packages/pdf-worker/dist ee/packages/pdf-worker/dist COPY ./packages/tools/package.json packages/tools/package.json COPY ./packages/tools/dist packages/tools/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index b88cf06956a4..61c86a741610 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -26,6 +26,7 @@ "@rocket.chat/omnichannel-services": "workspace:^", "@rocket.chat/pdf-worker": "workspace:^", "@rocket.chat/tools": "workspace:^", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", diff --git a/ee/apps/omnichannel-transcript/src/service.ts b/ee/apps/omnichannel-transcript/src/service.ts index ad60687d5ba4..e313c0069e8d 100644 --- a/ee/apps/omnichannel-transcript/src/service.ts +++ b/ee/apps/omnichannel-transcript/src/service.ts @@ -1,10 +1,13 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; +startTracing({ service: 'omnichannel-transcript' }); + const PORT = process.env.PORT || 3036; (async () => { diff --git a/ee/apps/presence-service/Dockerfile b/ee/apps/presence-service/Dockerfile index fd1fc0741bcf..ce1977fd3c87 100644 --- a/ee/apps/presence-service/Dockerfile +++ b/ee/apps/presence-service/Dockerfile @@ -50,6 +50,9 @@ COPY ./ee/packages/network-broker/dist ee/packages/network-broker/dist COPY ./ee/packages/license/package.json packages/license/package.json COPY ./ee/packages/license/dist packages/license/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json index d4d8883596db..2b3b08de5078 100644 --- a/ee/apps/presence-service/package.json +++ b/ee/apps/presence-service/package.json @@ -23,6 +23,7 @@ "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/presence": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", diff --git a/ee/apps/presence-service/src/service.ts b/ee/apps/presence-service/src/service.ts index 0c51c30dc577..5114c712dae7 100755 --- a/ee/apps/presence-service/src/service.ts +++ b/ee/apps/presence-service/src/service.ts @@ -1,9 +1,12 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; +startTracing({ service: 'presence-service' }); + const PORT = process.env.PORT || 3031; (async () => { diff --git a/ee/apps/queue-worker/Dockerfile b/ee/apps/queue-worker/Dockerfile index 7c3e0eaaa070..b2595b06f1fe 100644 --- a/ee/apps/queue-worker/Dockerfile +++ b/ee/apps/queue-worker/Dockerfile @@ -56,6 +56,9 @@ COPY ./ee/packages/pdf-worker/dist ee/packages/pdf-worker/dist COPY ./packages/tools/package.json packages/tools/package.json COPY ./packages/tools/dist packages/tools/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index f1853bc41218..5c3e91f2b8ac 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -23,6 +23,7 @@ "@rocket.chat/models": "workspace:^", "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/omnichannel-services": "workspace:^", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", diff --git a/ee/apps/queue-worker/src/service.ts b/ee/apps/queue-worker/src/service.ts index c11376d56534..a66d1a579bad 100644 --- a/ee/apps/queue-worker/src/service.ts +++ b/ee/apps/queue-worker/src/service.ts @@ -1,10 +1,13 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; +startTracing({ service: 'queue-worker' }); + const PORT = process.env.PORT || 3038; (async () => { diff --git a/ee/apps/stream-hub-service/Dockerfile b/ee/apps/stream-hub-service/Dockerfile index 612dbe5d73c9..792afc778088 100644 --- a/ee/apps/stream-hub-service/Dockerfile +++ b/ee/apps/stream-hub-service/Dockerfile @@ -47,6 +47,9 @@ COPY ./ee/packages/network-broker/dist ee/packages/network-broker/dist COPY ./ee/packages/license/package.json packages/license/package.json COPY ./ee/packages/license/dist packages/license/dist +COPY ./packages/tracing/package.json packages/tracing/package.json +COPY ./packages/tracing/dist packages/tracing/dist + COPY ./packages/ui-kit/package.json packages/ui-kit/package.json COPY ./packages/ui-kit/dist packages/ui-kit/dist diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json index 382b252a3bf9..601608c6d6d8 100644 --- a/ee/apps/stream-hub-service/package.json +++ b/ee/apps/stream-hub-service/package.json @@ -23,6 +23,7 @@ "@rocket.chat/models": "workspace:^", "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", + "@rocket.chat/tracing": "workspace:^", "@types/node": "^14.18.63", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", diff --git a/ee/apps/stream-hub-service/src/service.ts b/ee/apps/stream-hub-service/src/service.ts index 5e035548dc38..26cea9676a02 100755 --- a/ee/apps/stream-hub-service/src/service.ts +++ b/ee/apps/stream-hub-service/src/service.ts @@ -1,12 +1,15 @@ import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { broker } from '@rocket.chat/network-broker'; +import { startTracing } from '@rocket.chat/tracing'; import polka from 'polka'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; import { DatabaseWatcher } from '../../../../apps/meteor/server/database/DatabaseWatcher'; import { StreamHub } from './StreamHub'; +startTracing({ service: 'stream-hub-service' }); + const PORT = process.env.PORT || 3035; (async () => { diff --git a/ee/packages/network-broker/src/NetworkBroker.ts b/ee/packages/network-broker/src/NetworkBroker.ts index e326357cba0a..64b690c3eff6 100644 --- a/ee/packages/network-broker/src/NetworkBroker.ts +++ b/ee/packages/network-broker/src/NetworkBroker.ts @@ -1,5 +1,6 @@ import { asyncLocalStorage } from '@rocket.chat/core-services'; import type { IBroker, IBrokerNode, IServiceMetrics, IServiceClass, EventSignatures } from '@rocket.chat/core-services'; +import { injectCurrentContext, tracerSpan } from '@rocket.chat/tracing'; import type { ServiceBroker, Context, ServiceSchema } from 'moleculer'; import { EnterpriseCheck } from './EnterpriseCheck'; @@ -52,7 +53,12 @@ export class NetworkBroker implements IBroker { if (!services.find((service) => service.name === method.split('.')[0])) { return new Error('method-not-available'); } - return this.broker.call(method, data); + + return this.broker.call(method, data, { + meta: { + optl: injectCurrentContext(), + }, + }); } async waitAndCall(method: string, data: any): Promise { @@ -72,7 +78,11 @@ export class NetworkBroker implements IBroker { return context.ctx.call(method, data); } - return this.broker.call(method, data); + return this.broker.call(method, data, { + meta: { + optl: injectCurrentContext(), + }, + }); } async destroyService(instance: IServiceClass): Promise { @@ -148,16 +158,23 @@ export class NetworkBroker implements IBroker { continue; } - service.actions[method] = async (ctx: Context<[]>): Promise => { - return asyncLocalStorage.run( - { - id: ctx.id, - nodeID: ctx.nodeID, - requestID: ctx.requestID, - broker: this, - ctx, + service.actions[method] = async (ctx: Context<[], { optl?: unknown }>): Promise => { + return tracerSpan( + `action ${name}:${method}`, + {}, + () => { + return asyncLocalStorage.run( + { + id: ctx.id, + nodeID: ctx.nodeID, + requestID: ctx.requestID, + broker: this, + ctx, + }, + () => serviceInstance[method](...ctx.params), + ); }, - () => serviceInstance[method](...ctx.params), + ctx.meta?.optl, ); }; } diff --git a/ee/packages/network-broker/src/index.ts b/ee/packages/network-broker/src/index.ts index caa12890b514..8dcd99dac07d 100644 --- a/ee/packages/network-broker/src/index.ts +++ b/ee/packages/network-broker/src/index.ts @@ -27,7 +27,6 @@ const { BULKHEAD_MAX_QUEUE_SIZE = '10000', MS_METRICS = 'false', MS_METRICS_PORT = '9458', - TRACING_ENABLED = 'false', SKIP_PROCESS_EVENT_REGISTRATION = 'false', } = process.env; @@ -143,27 +142,6 @@ const network = new ServiceBroker({ maxQueueSize: parseInt(BULKHEAD_MAX_QUEUE_SIZE), }, - tracing: { - enabled: TRACING_ENABLED === 'true', - exporter: { - type: 'Jaeger', - options: { - endpoint: null, - host: 'jaeger', - port: 6832, - sampler: { - // Sampler type. More info: https://www.jaegertracing.io/docs/1.14/sampling/#client-sampling-configuration - type: 'Const', - // Sampler specific options. - options: {}, - }, - // Additional options for `Jaeger.Tracer` - tracerOptions: {}, - // Default tags. They will be added into all span tags. - defaultTags: null, - }, - }, - }, errorRegenerator: new CustomRegenerator(), started(): void { console.log('NetworkBroker started successfully.'); diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 4f81be0fac02..e9487a8b3c72 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -38,6 +38,7 @@ "@rocket.chat/message-parser": "workspace:^", "@rocket.chat/models": "workspace:^", "@rocket.chat/rest-typings": "workspace:^", + "@rocket.chat/tracing": "workspace:^", "@rocket.chat/ui-kit": "workspace:~" } } diff --git a/packages/core-services/src/index.ts b/packages/core-services/src/index.ts index 34694b078e01..c9cfa82caabb 100644 --- a/packages/core-services/src/index.ts +++ b/packages/core-services/src/index.ts @@ -51,6 +51,7 @@ import type { IVoipFreeSwitchService } from './types/IVoipFreeSwitchService'; import type { IVoipService } from './types/IVoipService'; export { asyncLocalStorage } from './lib/asyncLocalStorage'; +export { traceInstanceMethods } from './lib/asyncMethodCallContext'; export { MeteorError, isMeteorError } from './MeteorError'; export { api } from './api'; export { EventSignatures } from './events/Events'; diff --git a/packages/core-services/src/lib/asyncMethodCallContext.ts b/packages/core-services/src/lib/asyncMethodCallContext.ts new file mode 100644 index 000000000000..cd9f19baf976 --- /dev/null +++ b/packages/core-services/src/lib/asyncMethodCallContext.ts @@ -0,0 +1,44 @@ +import { tracerActiveSpan } from '@rocket.chat/tracing'; + +const getArguments = (args: any[]): any[] => { + return args.map((arg) => { + if (typeof arg === 'object' && arg != null && 'session' in arg) { + return '[mongo options with session]'; + } + return arg; + }); +}; + +export function traceInstanceMethods(instance: T, ignoreMethods: string[] = []): T { + const className = instance.constructor.name; + + return new Proxy(instance, { + get(target: Record, prop: string): any { + if (typeof target[prop] === 'function' && !ignoreMethods.includes(prop)) { + return new Proxy(target[prop], { + apply: (target, thisArg, argumentsList): any => { + if (['doNotMixInclusionAndExclusionFields', 'ensureDefaultFields'].includes(prop)) { + return Reflect.apply(target, thisArg, argumentsList); + } + + return tracerActiveSpan( + `model ${className}.${prop}`, + { + attributes: { + model: className, + method: prop, + parameters: getArguments(argumentsList), + }, + }, + () => { + return Reflect.apply(target, thisArg, argumentsList); + }, + ); + }, + }); + } + + return Reflect.get(target, prop); + }, + }) as T; +} diff --git a/packages/core-services/src/lib/mongo.ts b/packages/core-services/src/lib/mongo.ts index fab1fd108d99..b41d838cd4a7 100644 --- a/packages/core-services/src/lib/mongo.ts +++ b/packages/core-services/src/lib/mongo.ts @@ -1,3 +1,4 @@ +import { initDatabaseTracing, isTracingEnabled } from '@rocket.chat/tracing'; import { MongoClient } from 'mongodb'; import type { Db, Collection, MongoClientOptions, Document } from 'mongodb'; @@ -6,7 +7,10 @@ const { MONGO_URL = 'mongodb://localhost:27017/rocketchat' } = process.env; const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1]; function connectDb(options?: MongoClientOptions): Promise { - const client = new MongoClient(MONGO_URL, options); + const client = new MongoClient(MONGO_URL, { + ...options, + monitorCommands: isTracingEnabled(), + }); return client.connect().catch((error) => { // exits the process in case of any error @@ -29,6 +33,8 @@ export const getConnection = ((): ((options?: MongoClientOptions) => Promise db = client.db(name); } + initDatabaseTracing(client); + // if getConnection was called multiple times before it was connected, wait for the connection return client.db(name); }; diff --git a/packages/instance-status/package.json b/packages/instance-status/package.json index be23f8a44498..9afd2d88dc0d 100644 --- a/packages/instance-status/package.json +++ b/packages/instance-status/package.json @@ -22,6 +22,7 @@ "/dist" ], "dependencies": { - "@rocket.chat/models": "workspace:^" + "@rocket.chat/models": "workspace:^", + "@rocket.chat/tracing": "workspace:^" } } diff --git a/packages/instance-status/src/index.ts b/packages/instance-status/src/index.ts index 0e74e1a7ef4f..38109e01626d 100644 --- a/packages/instance-status/src/index.ts +++ b/packages/instance-status/src/index.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events'; import { InstanceStatus as InstanceStatusModel } from '@rocket.chat/models'; +import { tracerSpan } from '@rocket.chat/tracing'; import { v4 as uuidv4 } from 'uuid'; const events = new EventEmitter(); @@ -113,8 +114,8 @@ function start(interval?: number) { interval = interval || defaultPingInterval; - pingInterval = setInterval(function () { - ping(); + pingInterval = setInterval(async function () { + await tracerSpan('InstanceStatus.ping', {}, () => ping()); }, interval * 1000); } diff --git a/packages/tracing/.eslintrc.json b/packages/tracing/.eslintrc.json new file mode 100644 index 000000000000..a83aeda48e66 --- /dev/null +++ b/packages/tracing/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["@rocket.chat/eslint-config"], + "ignorePatterns": ["**/dist"] +} diff --git a/packages/tracing/package.json b/packages/tracing/package.json new file mode 100644 index 000000000000..ef19ad071b9f --- /dev/null +++ b/packages/tracing/package.json @@ -0,0 +1,32 @@ +{ + "name": "@rocket.chat/tracing", + "version": "0.0.1", + "private": true, + "devDependencies": { + "@types/jest": "~29.5.7", + "eslint": "~8.45.0", + "jest": "~29.6.4", + "ts-jest": "~29.1.1", + "typescript": "~5.3.3" + }, + "scripts": { + "lint": "eslint --ext .js,.jsx,.ts,.tsx .", + "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", + "testunit": "jest --passWithNoTests", + "dev": "tsc --watch --preserveWatchOutput", + "build": "rm -rf dist && tsc" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "/dist" + ], + "volta": { + "extends": "../../package.json" + }, + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.53.0", + "@opentelemetry/sdk-node": "^0.53.0" + } +} diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts new file mode 100644 index 000000000000..8e97dabc63c5 --- /dev/null +++ b/packages/tracing/src/index.ts @@ -0,0 +1,96 @@ +import { context, propagation, SpanStatusCode, trace } from '@opentelemetry/api'; +import type { Span, SpanOptions, Tracer } from '@opentelemetry/api'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; +import { NodeSDK } from '@opentelemetry/sdk-node'; + +export { initDatabaseTracing } from './traceDatabaseCalls'; + +let tracer: Tracer | undefined; + +export function isTracingEnabled() { + return ['yes', 'true'].includes(String(process.env.TRACING_ENABLED).toLowerCase()); +} + +export const startTracing = ({ service }: { service: string }) => { + const exporter = new OTLPTraceExporter(); + + const sdk = new NodeSDK({ + traceExporter: exporter, + instrumentations: [], + serviceName: service, + }); + sdk.start(); + + tracer = trace.getTracer(service); +}; + +export function tracerSpan ReturnType>( + name: string, + options: SpanOptions, + fn: F, + optl?: unknown, +): ReturnType { + if (!isTracingEnabled()) { + return fn(); + } + + if (!tracer) { + throw new Error(`Tracing is enabled but not started. You should call 'startTracing()' to fix this.`); + } + + const computeResult = (span: Span) => { + try { + const result = fn(span); + if (result instanceof Promise) { + result.catch((err) => { + span.recordException(err); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err.message, + }); + }); + + return result; + } + return result; + } catch (err: any) { + span.recordException(err); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err.message, + }); + throw err; + } finally { + span.end(); + } + }; + + if (optl) { + const activeContext = propagation.extract(context.active(), optl); + + return tracer.startActiveSpan(name, options, activeContext, computeResult); + } + + return tracer.startActiveSpan(name, options, computeResult); +} + +export function tracerActiveSpan ReturnType>( + name: string, + options: SpanOptions, + fn: F, + optl?: unknown, +): ReturnType { + const currentSpan = trace.getSpan(context.active()); + + if (process.env.LOG_UNTRACED_METHODS) { + console.log(`No active span for ${name}`, new Error().stack); + } + + return currentSpan ? tracerSpan(name, options, fn, optl) : fn(); +} + +export function injectCurrentContext() { + const output: Record = {}; + propagation.inject(context.active(), output); + return output; +} diff --git a/packages/tracing/src/traceDatabaseCalls.ts b/packages/tracing/src/traceDatabaseCalls.ts new file mode 100644 index 000000000000..6ddde0b39753 --- /dev/null +++ b/packages/tracing/src/traceDatabaseCalls.ts @@ -0,0 +1,65 @@ +import { trace, context, SpanStatusCode } from '@opentelemetry/api'; +import type { MongoClient } from 'mongodb'; + +import { isTracingEnabled } from '.'; + +const tracer = trace.getTracer('core'); + +export const initDatabaseTracing = (client: MongoClient) => { + if (!isTracingEnabled()) { + return; + } + + const DurationStart = new Map(); + + client.on('commandStarted', (event) => { + const collection = event.command[event.commandName]; + + const currentSpan = trace.getSpan(context.active()); + if (currentSpan) { + const span = tracer.startSpan(`mongodb ${collection}.${event.commandName}`, { + attributes: { + 'db.connection_string': event.address, + 'db.mongodb.collection': collection, + 'db.name': event.databaseName, + 'db.operation': event.commandName, + 'db.statement': JSON.stringify(event.command, null, 2), + 'db.system': 'mongodb', + // net.peer.name + // net.peer.port + }, + }); + + DurationStart.set(event.requestId, { event, span }); + } + }); + + client.on('commandSucceeded', (event) => { + if (!DurationStart.has(event.requestId)) { + return; + } + + const { span } = DurationStart.get(event.requestId); + DurationStart.delete(event.requestId); + + span.end(); + }); + + client.on('commandFailed', (event) => { + if (!DurationStart.has(event.requestId)) { + return; + } + + const { span } = DurationStart.get(event.requestId); + + DurationStart.delete(event.requestId); + + span.recordException(event.failure); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: event.failure.message, + }); + + span.end(); + }); +}; diff --git a/packages/tracing/tsconfig.json b/packages/tracing/tsconfig.json new file mode 100644 index 000000000000..52e9dd8c4976 --- /dev/null +++ b/packages/tracing/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.server.json", + "compilerOptions": { + "declaration": true, + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src/**/*"] +} diff --git a/tsconfig.base.server.json b/tsconfig.base.server.json index a7a87f033d7f..6864b82a7f82 100644 --- a/tsconfig.base.server.json +++ b/tsconfig.base.server.json @@ -6,5 +6,8 @@ "module": "commonjs", "sourceMap": true, }, - "exclude": ["node_modules", "**/*.spec.ts"] + "exclude": ["node_modules", "**/*.spec.ts"], + "ts-node": { + "transpileOnly": true + } } diff --git a/yarn.lock b/yarn.lock index bdb87b964721..7f22ffc44c08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4008,6 +4008,30 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.7.1": + version: 1.12.2 + resolution: "@grpc/grpc-js@npm:1.12.2" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/0d0556da8515704b5e722b86097e04693d8c71ba286a076270a96e1ac3a4950e87559c718cc2875d3fcaa6cb8e07d0cc6b1db2673b8940829dfe8b75197844dd + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.13": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/7e2d842c2061cbaf6450c71da0077263be3bab165454d5c8a3e1ae4d3c6d2915f02fd27da63ff01f05e127b1221acd40705273f5d29303901e60514e852992f4 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.10": version: 0.11.10 resolution: "@humanwhocodes/config-array@npm:0.11.10" @@ -4192,7 +4216,7 @@ __metadata: languageName: node linkType: hard -"@jest/core@npm:^29.7.0": +"@jest/core@npm:^29.6.4, @jest/core@npm:^29.7.0": version: 29.7.0 resolution: "@jest/core@npm:29.7.0" dependencies: @@ -4489,6 +4513,13 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: 10/ac64e3f0615ecc015461c9f527f124d2edaa9e68de153c1e270c627e01e83d046522d7e872692fd57a8c514578b539afceff75831c0d8b2a9a7a347fbed35af4 + languageName: node + linkType: hard + "@jsonjoy.com/base64@npm:^1.1.1": version: 1.1.2 resolution: "@jsonjoy.com/base64@npm:1.1.2" @@ -5295,13 +5326,329 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api@npm:^1.4.0": +"@opentelemetry/api-logs@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/api-logs@npm:0.53.0" + dependencies: + "@opentelemetry/api": "npm:^1.0.0" + checksum: 10/347b4554d6ee01afb29bd39e8f9cbbccd80abb0883fe6a84e3bcce8ab4dbfe357a2729246d2f66de0de6272846fd1bb2d71e286e18ad2690d9e7f46f02f00f73 + languageName: node + linkType: hard + +"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 languageName: node linkType: hard +"@opentelemetry/context-async-hooks@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/c8824cc00385f21ecdf5b48ac474096687f9ce2e8d34612a62ee8bc7a6e25797c787239349a12bfeefbff200dcb7379ca45355a5684b9755dcf8fbd3b69cf523 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/core@npm:1.26.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/474b6bcf42cd2825d56f915eb0d6e6cdcb37777a11fc2618fc2fa50754f4b9b5df23944f3aab186cb3ab930db5c3a81efa3183362802314a966930110346e6a4 + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.53.0" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/sdk-logs": "npm:0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/4a8236acffe847d95ffb9098efdded74ff9ccc1e4f5ad68d7cc110f14a8a29841e2c9e5ee201f38bb49602690bd197c9ef3536ae3f23c012ce3248a65327f2bb + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-http@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/sdk-logs": "npm:0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/bf0d19a71eed3d90f51f6ab1c5fdbb7837477db50b47a46944efeab42c6d72ef13677487eb1ed4cd0d6e6fccbd41cec33a31e17aaef0e24f9bad5b52ab8b3649 + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-proto@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-logs": "npm:0.53.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/295f73fb7c098e54d83149ab1a681f8f16b379111dc23201b7eaa803eff6ba42bebd99a00df1a02112bd181b341588e8de3e09364b9f28d2c91d8bd2be4824a7 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.53.0, @opentelemetry/exporter-trace-otlp-grpc@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.53.0" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/3c8a854f9c401549ead8a900d4773de891663ef6233e457d57557834cacc547a637dccf0f4c63e9b367f33925f5a4991e37910566681481169ee17e5253b6894 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-http@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.53.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/28c75e25564833bc448b5733415730483c9f28714577acb679087d5ccfc46d74b3f24996c41f2c93bf6a6406edb1cad7e8cf2a76b61096e3f417f90044e1d795 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-proto@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.53.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/2d4651db9ef5d5b1b22b84a6d268ae4e020bf107a71f7d96a8b149f3af4680d6087dfdabf3b3a606f862888ccc63bfe4368057c45befc58c60bda48eceab50ea + languageName: node + linkType: hard + +"@opentelemetry/exporter-zipkin@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/exporter-zipkin@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/155a450a17f1963667dd6b20150858ebcdc9bc358396b2d3df728faa7d66fed68e6c2f0c4cfb4598d3f03aa7ad2ba280e2af033245289d7a5d98b6e5ccd7c54a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 + languageName: node + linkType: hard + +"@opentelemetry/otlp-exporter-base@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.53.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/ca59d73ae8f83946062b060a9a382fc7db6154c892ed56b6ab7f545530ba4850b4d0a748daaa30d1177ef6a8c2a0fddd34a199080f4474ec445944cece86f1ef + languageName: node + linkType: hard + +"@opentelemetry/otlp-grpc-exporter-base@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.53.0" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/otlp-exporter-base": "npm:0.53.0" + "@opentelemetry/otlp-transformer": "npm:0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/412e0428946277b7fbfb7ceafd9624fa930cbc9ff892cc0f796f712ee4b1a6d53516a2891bce5ffc9e72a209b32953d4d87e726e55c9ea422dc75ed580c0af37 + languageName: node + linkType: hard + +"@opentelemetry/otlp-transformer@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-logs": "npm:0.53.0" + "@opentelemetry/sdk-metrics": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + protobufjs: "npm:^7.3.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/578cf13d7984a0b1ba1db3d86d1e358bf70e8b534166f8327a10fccca0afd3900896a80e5e73caae61837b0cbc99d81b44784edee68a3517d73f5330a3624ccd + languageName: node + linkType: hard + +"@opentelemetry/propagator-b3@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/propagator-b3@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/fa99958ccd7e2d8140c6271a6c2bc0b95054691ce227b75272951bb1f387bd442ee0813b5f5e268c837d3a563c36ac516bac37aa821e1b5119c7f21c90742c89 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/propagator-jaeger@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/f895186e6c95a1ca9cc172de50867c036f87dacdfd9206df2ab35710134221b0021974eb2ced2453840a4fbb6aae55a732847b898e420b3871b3157eb81183ed + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/resources@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/ce60dbf2bd424b01824b72f533724eaf64418e01c43bef952b87dbff6d2a0f28cdcbea0d3d95c5e324f609e58721bf52ea91b5518b0e30d6bb03fb95af85cc33 + languageName: node + linkType: hard + +"@opentelemetry/sdk-logs@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/sdk-logs@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/resources": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.10.0" + checksum: 10/b11b512820f3d55288f7478831587ebe2e7077980f060a779a13848c62cab30023734857c68ef110eebe961884cb8892d7c77841a5f1d22c2426cbb18d762975 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/sdk-metrics@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/resources": "npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/e48e4dd1fed1e501750460e1320f89507c19287c5059cfaccc8268ad8cc3e1de40feeee6584b23626e01f9cde0f10301d08edf6a65bbd1346ef94f70ae8844f5 + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/sdk-node@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.53.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.53.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.53.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.53.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.53.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.53.0" + "@opentelemetry/exporter-zipkin": "npm:1.26.0" + "@opentelemetry/instrumentation": "npm:0.53.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/sdk-logs": "npm:0.53.0" + "@opentelemetry/sdk-metrics": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + "@opentelemetry/sdk-trace-node": "npm:1.26.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/ce05610b11336ad8218a39c1798d7090e1c1666956f37347b585e5e51e6e62b0cd24207474fa312528c10c8473dc6cbaefb6fe3647d76613c241795e1cfe6303 + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.26.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/resources": "npm:1.26.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/e4a3d296ad908b9f58d7aefdcc1f7383fb0eb64fc85b0b5d18c4a7d829ce3d0efa5e53f5fe1a23185d9b5d97b782431384efe01aba8ba788922260a9dbbdb662 + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-node@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/sdk-trace-node@npm:1.26.0" + dependencies: + "@opentelemetry/context-async-hooks": "npm:1.26.0" + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/propagator-b3": "npm:1.26.0" + "@opentelemetry/propagator-jaeger": "npm:1.26.0" + "@opentelemetry/sdk-trace-base": "npm:1.26.0" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/ac89d54b65e10928a13e3c0adf4d4997d53baf8b03f376c03d47e921322959a0f8cfae744f4bdc63bfd1ab22d67fcae8d7bd1f6bbfbaad18cd1623543128ac54 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.27.0": + version: 1.27.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" + checksum: 10/98166522f299e2fe3d43376adbdeb92679b75ebb172e2a3c4c71f2942bd91585e9537618efbbae6dc08177699e5719368edf66d7e69e8636f360b85217bbdbe1 + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.4.1": version: 2.4.1 resolution: "@parcel/watcher-android-arm64@npm:2.4.1" @@ -5468,6 +5815,79 @@ __metadata: languageName: node linkType: hard +"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/aspromise@npm:1.1.2" + checksum: 10/8a938d84fe4889411296db66b29287bd61ea3c14c2d23e7a8325f46a2b8ce899857c5f038d65d7641805e6c1d06b495525c7faf00c44f85a7ee6476649034969 + languageName: node + linkType: hard + +"@protobufjs/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/base64@npm:1.1.2" + checksum: 10/c71b100daeb3c9bdccab5cbc29495b906ba0ae22ceedc200e1ba49717d9c4ab15a6256839cebb6f9c6acae4ed7c25c67e0a95e734f612b258261d1a3098fe342 + languageName: node + linkType: hard + +"@protobufjs/codegen@npm:^2.0.4": + version: 2.0.4 + resolution: "@protobufjs/codegen@npm:2.0.4" + checksum: 10/c6ee5fa172a8464f5253174d3c2353ea520c2573ad7b6476983d9b1346f4d8f2b44aa29feb17a949b83c1816bc35286a5ea265ed9d8fdd2865acfa09668c0447 + languageName: node + linkType: hard + +"@protobufjs/eventemitter@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/eventemitter@npm:1.1.0" + checksum: 10/03af3e99f17ad421283d054c88a06a30a615922a817741b43ca1b13e7c6b37820a37f6eba9980fb5150c54dba6e26cb6f7b64a6f7d8afa83596fafb3afa218c3 + languageName: node + linkType: hard + +"@protobufjs/fetch@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/fetch@npm:1.1.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.1" + "@protobufjs/inquire": "npm:^1.1.0" + checksum: 10/67ae40572ad536e4ef94269199f252c024b66e3059850906bdaee161ca1d75c73d04d35cd56f147a8a5a079f5808e342b99e61942c1dae15604ff0600b09a958 + languageName: node + linkType: hard + +"@protobufjs/float@npm:^1.0.2": + version: 1.0.2 + resolution: "@protobufjs/float@npm:1.0.2" + checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 + languageName: node + linkType: hard + +"@protobufjs/inquire@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/inquire@npm:1.1.0" + checksum: 10/c09efa34a5465cb120775e1a482136f2340a58b4abce7e93d72b8b5a9324a0e879275016ef9fcd73d72a4731639c54f2bb755bb82f916e4a78892d1d840bb3d2 + languageName: node + linkType: hard + +"@protobufjs/path@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/path@npm:1.1.2" + checksum: 10/bb709567935fd385a86ad1f575aea98131bbd719c743fb9b6edd6b47ede429ff71a801cecbd64fc72deebf4e08b8f1bd8062793178cdaed3713b8d15771f9b83 + languageName: node + linkType: hard + +"@protobufjs/pool@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/pool@npm:1.1.0" + checksum: 10/b9c7047647f6af28e92aac54f6f7c1f7ff31b201b4bfcc7a415b2861528854fce3ec666d7e7e10fd744da905f7d4aef2205bbcc8944ca0ca7a82e18134d00c46 + languageName: node + linkType: hard + +"@protobufjs/utf8@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/utf8@npm:1.1.0" + checksum: 10/131e289c57534c1d73a0e55782d6751dd821db1583cb2f7f7e017c9d6747addaebe79f28120b2e0185395d990aad347fb14ffa73ef4096fa38508d61a0e64602 + languageName: node + linkType: hard + "@react-aria/breadcrumbs@npm:^3.5.0": version: 3.5.1 resolution: "@react-aria/breadcrumbs@npm:3.5.1" @@ -7607,6 +8027,7 @@ __metadata: "@rocket.chat/rest-typings": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tools": "workspace:^" + "@rocket.chat/tracing": "workspace:^" "@types/bcrypt": "npm:^5.0.2" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" @@ -7742,6 +8163,7 @@ __metadata: "@rocket.chat/network-broker": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" + "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" "@types/polka": "npm:^0.5.7" @@ -7802,6 +8224,7 @@ __metadata: "@rocket.chat/message-parser": "workspace:^" "@rocket.chat/models": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" + "@rocket.chat/tracing": "workspace:^" "@rocket.chat/ui-kit": "workspace:~" "@types/jest": "npm:~29.5.13" babel-jest: "npm:^29.5.0" @@ -7903,6 +8326,7 @@ __metadata: "@rocket.chat/network-broker": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" + "@rocket.chat/tracing": "workspace:^" "@types/ejson": "npm:^2.2.2" "@types/gc-stats": "npm:^1.4.3" "@types/meteor": "npm:^2.9.8" @@ -8217,6 +8641,7 @@ __metadata: dependencies: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/models": "workspace:^" + "@rocket.chat/tracing": "workspace:^" eslint: "npm:~8.45.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" prettier: "npm:~2.8.8" @@ -8487,6 +8912,9 @@ __metadata: "@nivo/heatmap": "npm:0.84.0" "@nivo/line": "npm:0.84.0" "@nivo/pie": "npm:0.84.0" + "@opentelemetry/api": "npm:^1.9.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:^0.53.0" + "@opentelemetry/sdk-node": "npm:^0.53.0" "@playwright/test": "npm:^1.40.1" "@react-aria/color": "npm:^3.0.0-beta.15" "@react-aria/toolbar": "npm:^3.0.0-beta.1" @@ -8545,6 +8973,7 @@ __metadata: "@rocket.chat/sha256": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tools": "workspace:^" + "@rocket.chat/tracing": "workspace:^" "@rocket.chat/ui-avatar": "workspace:^" "@rocket.chat/ui-client": "workspace:^" "@rocket.chat/ui-composer": "workspace:^" @@ -8974,6 +9403,7 @@ __metadata: "@rocket.chat/omnichannel-services": "workspace:^" "@rocket.chat/pdf-worker": "workspace:^" "@rocket.chat/tools": "workspace:^" + "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" "@types/polka": "npm:^0.5.7" @@ -9114,6 +9544,7 @@ __metadata: "@rocket.chat/network-broker": "workspace:^" "@rocket.chat/presence": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" + "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" "@types/polka": "npm:^0.5.7" @@ -9177,6 +9608,7 @@ __metadata: "@rocket.chat/models": "workspace:^" "@rocket.chat/network-broker": "workspace:^" "@rocket.chat/omnichannel-services": "workspace:^" + "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" "@types/polka": "npm:^0.5.7" @@ -9336,6 +9768,7 @@ __metadata: "@rocket.chat/network-broker": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" "@rocket.chat/string-helpers": "npm:~0.31.25" + "@rocket.chat/tracing": "workspace:^" "@types/bcrypt": "npm:^5.0.2" "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:^14.18.63" @@ -9396,6 +9829,21 @@ __metadata: languageName: unknown linkType: soft +"@rocket.chat/tracing@workspace:^, @rocket.chat/tracing@workspace:packages/tracing": + version: 0.0.0-use.local + resolution: "@rocket.chat/tracing@workspace:packages/tracing" + dependencies: + "@opentelemetry/api": "npm:^1.9.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:^0.53.0" + "@opentelemetry/sdk-node": "npm:^0.53.0" + "@types/jest": "npm:~29.5.7" + eslint: "npm:~8.45.0" + jest: "npm:~29.6.4" + ts-jest: "npm:~29.1.1" + typescript: "npm:~5.3.3" + languageName: unknown + linkType: soft + "@rocket.chat/ui-avatar@workspace:^, @rocket.chat/ui-avatar@workspace:packages/ui-avatar, @rocket.chat/ui-avatar@workspace:~": version: 0.0.0-use.local resolution: "@rocket.chat/ui-avatar@workspace:packages/ui-avatar" @@ -11913,7 +12361,7 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:*, @types/jest@npm:~29.5.12, @types/jest@npm:~29.5.13": +"@types/jest@npm:*, @types/jest@npm:~29.5.12, @types/jest@npm:~29.5.13, @types/jest@npm:~29.5.7": version: 29.5.13 resolution: "@types/jest@npm:29.5.13" dependencies: @@ -12294,6 +12742,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:>=13.7.0, @types/node@npm:^22.0.0": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10/e8ba102f8c1aa7623787d625389be68d64e54fcbb76d41f6c2c64e8cf4c9f4a2370e7ef5e5f1732f3c57529d3d26afdcb2edc0101c5e413a79081449825c57ac + languageName: node + linkType: hard + "@types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" @@ -12324,15 +12781,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.0.0": - version: 22.7.5 - resolution: "@types/node@npm:22.7.5" - dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/e8ba102f8c1aa7623787d625389be68d64e54fcbb76d41f6c2c64e8cf4c9f4a2370e7ef5e5f1732f3c57529d3d26afdcb2edc0101c5e413a79081449825c57ac - languageName: node - linkType: hard - "@types/nodemailer@npm:*, @types/nodemailer@npm:^6.4.15": version: 6.4.15 resolution: "@types/nodemailer@npm:6.4.15" @@ -12664,6 +13112,13 @@ __metadata: languageName: node linkType: hard +"@types/shimmer@npm:^1.2.0": + version: 1.2.0 + resolution: "@types/shimmer@npm:1.2.0" + checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c + languageName: node + linkType: hard + "@types/sinon@npm:^10.0.20": version: 10.0.20 resolution: "@types/sinon@npm:10.0.20" @@ -16565,7 +17020,7 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.3": +"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2, cjs-module-lexer@npm:^1.2.3": version: 1.4.1 resolution: "cjs-module-lexer@npm:1.4.1" checksum: 10/6e830a1e00a34d416949bbc1924f3e8da65cef4a6a09e2b7fa35722e2d1c34bf378d3baca987b698d1cbc3eb83e44b044039b4e82755c96f30e0f03d1d227637 @@ -18340,7 +18795,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.7": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.7": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -23647,6 +24102,18 @@ __metadata: languageName: node linkType: hard +"import-in-the-middle@npm:^1.8.1": + version: 1.11.2 + resolution: "import-in-the-middle@npm:1.11.2" + dependencies: + acorn: "npm:^8.8.2" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10/ebd1aaba4441e54db124670e13038127f5283b686d83276dc004cd9d3bb1747e63ac37935c3c58885b52aedf48e669093d24ffe4b5849adef744d79ee67445ad + languageName: node + linkType: hard + "import-lazy@npm:^2.1.0": version: 2.1.0 resolution: "import-lazy@npm:2.1.0" @@ -25026,7 +25493,7 @@ __metadata: languageName: node linkType: hard -"jest-cli@npm:^29.7.0": +"jest-cli@npm:^29.6.4, jest-cli@npm:^29.7.0": version: 29.7.0 resolution: "jest-cli@npm:29.7.0" dependencies: @@ -25501,6 +25968,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:~29.6.4": + version: 29.6.4 + resolution: "jest@npm:29.6.4" + dependencies: + "@jest/core": "npm:^29.6.4" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.6.4" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10/d747e293bd63f583e7978ac0693ab7a019812fa44b9bf3b3fe20e75e8a343bcd8251d292326d73151dc0b8a2b5a974d878b3aa9ffb146dfa7980553f64a35b43 + languageName: node + linkType: hard + "jiti@npm:^1.20.0": version: 1.21.6 resolution: "jiti@npm:1.21.6" @@ -26420,6 +26906,13 @@ __metadata: languageName: node linkType: hard +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10/c301cc379310441dc73cd6cebeb91fb254bea74e6ad3027f9346fc43b4174385153df420ffa521654e502fd34c40ef69ca4e7d40ee7129a99e06f306032bfc65 + languageName: node + linkType: hard + "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -26674,6 +27167,13 @@ __metadata: languageName: node linkType: hard +"long@npm:^5.0.0": + version: 5.2.3 + resolution: "long@npm:5.2.3" + checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 + languageName: node + linkType: hard + "long@npm:~3": version: 3.2.0 resolution: "long@npm:3.2.0" @@ -27852,6 +28352,13 @@ __metadata: languageName: node linkType: hard +"module-details-from-path@npm:^1.0.3": + version: 1.0.3 + resolution: "module-details-from-path@npm:1.0.3" + checksum: 10/f93226e9154fc8cb91f4609b639167ec7ad9155b30be4924d9717656648a3ae5f181d4e2338434d4c5afc7b5f4c10dd3b64109e5b89a4be70b20a25ba3573d54 + languageName: node + linkType: hard + "module-not-found-error@npm:^1.0.1": version: 1.0.1 resolution: "module-not-found-error@npm:1.0.1" @@ -31359,6 +31866,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.2.5, protobufjs@npm:^7.3.0": + version: 7.4.0 + resolution: "protobufjs@npm:7.4.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 10/408423506610f70858d7593632f4a6aa4f05796c90fd632be9b9252457c795acc71aa6d3b54bb7f48a890141728fee4ca3906723ccea6c202ad71f21b3879b8b + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -32919,6 +33446,17 @@ __metadata: languageName: node linkType: hard +"require-in-the-middle@npm:^7.1.1": + version: 7.4.0 + resolution: "require-in-the-middle@npm:7.4.0" + dependencies: + debug: "npm:^4.3.5" + module-details-from-path: "npm:^1.0.3" + resolve: "npm:^1.22.8" + checksum: 10/0ca30ad6a6183423f38599709fc8a670682db85b581a66cb31ea31342e8ba2ce7dca44ee29e8cfe4fb59ffcb0c2b0f9b77d44a10cdc7535c7c2907028e53afbf + languageName: node + linkType: hard + "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -33656,7 +34194,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.2, semver@npm:^7.6.3": +"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.2, semver@npm:^7.6.3": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -33999,6 +34537,13 @@ __metadata: languageName: node linkType: hard +"shimmer@npm:^1.2.1": + version: 1.2.1 + resolution: "shimmer@npm:1.2.1" + checksum: 10/aa0d6252ad1c682a4fdfda69e541be987f7a265ac7b00b1208e5e48cc68dc55f293955346ea4c71a169b7324b82c70f8400b3d3d2d60b2a7519f0a3522423250 + languageName: node + linkType: hard + "side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": version: 1.0.6 resolution: "side-channel@npm:1.0.6" @@ -36261,7 +36806,7 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:~29.1.5": +"ts-jest@npm:~29.1.1, ts-jest@npm:~29.1.5": version: 29.1.5 resolution: "ts-jest@npm:29.1.5" dependencies: @@ -38976,7 +39521,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1, yargs@npm:^17.7.1": +"yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 085fa5e8643c67f06801cf4a8ccf2aeae3695123 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 18 Oct 2024 21:27:30 -0600 Subject: [PATCH 04/12] chore: Update typescript to 5.6 (#33665) Co-authored-by: Tasso --- .../meteor-accounts-saml/server/lib/SAML.ts | 4 +- .../useVideoConfMenuOptions.tsx | 2 +- .../omnichannel/triggers/EditTrigger.tsx | 2 +- .../server/lib/canned-responses.js | 15 + apps/meteor/ee/server/services/package.json | 4 +- apps/meteor/package.json | 8 +- apps/meteor/server/email/IMAPInterceptor.ts | 5 +- .../tests/end-to-end/api/custom-sounds.ts | 2 +- .../tests/end-to-end/api/emoji-custom.ts | 2 +- apps/meteor/tests/end-to-end/api/rooms.ts | 8 +- apps/meteor/tests/end-to-end/teardown.ts | 4 +- apps/uikit-playground/package.json | 2 +- ee/apps/account-service/package.json | 4 +- ee/apps/authorization-service/package.json | 4 +- ee/apps/ddp-streamer/package.json | 4 +- ee/apps/omnichannel-transcript/package.json | 4 +- ee/apps/presence-service/package.json | 4 +- ee/apps/queue-worker/package.json | 4 +- ee/apps/stream-hub-service/package.json | 4 +- ee/packages/license/package.json | 2 +- ee/packages/network-broker/package.json | 4 +- ee/packages/omnichannel-services/package.json | 4 +- ee/packages/pdf-worker/package.json | 2 +- ee/packages/presence/package.json | 4 +- ee/packages/ui-theming/package.json | 2 +- packages/account-utils/package.json | 2 +- packages/agenda/package.json | 2 +- packages/api-client/package.json | 2 +- packages/apps-engine/package.json | 2 +- packages/apps/package.json | 2 +- packages/base64/package.json | 2 +- packages/cas-validate/package.json | 2 +- packages/core-services/package.json | 2 +- packages/core-typings/package.json | 2 +- packages/cron/package.json | 2 +- packages/ddp-client/package.json | 2 +- packages/favicon/package.json | 2 +- packages/fuselage-ui-kit/package.json | 2 +- packages/gazzodown/package.json | 2 +- packages/i18n/package.json | 2 +- packages/i18n/src/index.mjs | 2 +- packages/instance-status/package.json | 2 +- packages/jest-presets/package.json | 2 +- packages/jwt/package.json | 2 +- packages/livechat/package.json | 2 +- packages/log-format/package.json | 2 +- packages/logger/package.json | 2 +- packages/message-parser/package.json | 4 +- packages/mock-providers/package.json | 2 +- packages/model-typings/package.json | 2 +- packages/models/package.json | 2 +- packages/node-poplib/package.json | 2 +- packages/password-policies/package.json | 2 +- packages/patch-injection/package.json | 2 +- packages/peggy-loader/package.json | 4 +- packages/random/package.json | 2 +- packages/release-action/package.json | 4 +- packages/release-changelog/package.json | 4 +- packages/rest-typings/package.json | 2 +- .../server-cloud-communication/package.json | 2 +- packages/server-fetch/package.json | 2 +- packages/sha256/package.json | 2 +- packages/tools/package.json | 2 +- packages/ui-avatar/package.json | 2 +- packages/ui-client/package.json | 2 +- packages/ui-composer/package.json | 2 +- packages/ui-contexts/package.json | 2 +- packages/ui-kit/package.json | 2 +- packages/ui-video-conf/package.json | 2 +- packages/ui-voip/package.json | 2 +- packages/web-ui-registration/package.json | 2 +- yarn.lock | 308 +++++++++--------- 72 files changed, 254 insertions(+), 258 deletions(-) diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts index be9bef0f2e7a..03642cab1eda 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts @@ -267,7 +267,7 @@ export class SAML { throw new Meteor.Error('Unable to process Logout Request: missing request data.'); } - let timeoutHandler: NodeJS.Timer | null = null; + let timeoutHandler: NodeJS.Timeout | undefined = undefined; const redirect = (url?: string | undefined): void => { if (!timeoutHandler) { // If the handler is null, then we already ended the response; @@ -275,7 +275,7 @@ export class SAML { } clearTimeout(timeoutHandler); - timeoutHandler = null; + timeoutHandler = undefined; res.writeHead(302, { Location: url || Meteor.absoluteUrl(), diff --git a/apps/meteor/client/hooks/roomActions/useStartCallRoomAction/useVideoConfMenuOptions.tsx b/apps/meteor/client/hooks/roomActions/useStartCallRoomAction/useVideoConfMenuOptions.tsx index 13b92e7f44a5..49cd483f498e 100644 --- a/apps/meteor/client/hooks/roomActions/useStartCallRoomAction/useVideoConfMenuOptions.tsx +++ b/apps/meteor/client/hooks/roomActions/useStartCallRoomAction/useVideoConfMenuOptions.tsx @@ -18,7 +18,7 @@ const useVideoConfMenuOptions = () => { const user = useUser(); const federated = isRoomFederated(room); - const ownUser = room.uids?.length === 1 ?? false; + const ownUser = room.uids?.length === 1 || false; const permittedToPostReadonly = usePermission('post-readonly', room._id); const permittedToCallManagement = usePermission('call-management', room._id); diff --git a/apps/meteor/client/views/omnichannel/triggers/EditTrigger.tsx b/apps/meteor/client/views/omnichannel/triggers/EditTrigger.tsx index 6545d859a9f8..512978abc664 100644 --- a/apps/meteor/client/views/omnichannel/triggers/EditTrigger.tsx +++ b/apps/meteor/client/views/omnichannel/triggers/EditTrigger.tsx @@ -66,7 +66,7 @@ const getInitialValues = (triggerData: Serialized | undefined) name: triggerData?.name ?? '', description: triggerData?.description || '', enabled: triggerData?.enabled ?? true, - runOnce: !!triggerData?.runOnce ?? false, + runOnce: !!triggerData?.runOnce || false, conditions: triggerData?.conditions.map(({ name, value }) => ({ name: name || 'page-url', value: value || '' })) ?? [ DEFAULT_PAGE_URL_CONDITION, ], diff --git a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js index 58b809350a9a..f4970914e31a 100644 --- a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js +++ b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js @@ -48,6 +48,21 @@ export async function findAllCannedResponses({ userId }) { return cannedResponses; } +/** + * @param {Object} param0 + * @param {String} param0.userId + * @param {String} [param0.shortcut] + * @param {String} [param0.text] + * @param {String} [param0.departmentId] + * @param {String} [param0.scope] + * @param {String} [param0.createdBy] + * @param {String[]} [param0.tags] + * @param {Object} param0.options + * @param {Number} param0.options.offset + * @param {Number} param0.options.count + * @param {Object} param0.options.sort + * @param {Object} param0.options.fields + */ export async function findAllCannedResponsesFilter({ userId, shortcut, text, departmentId, scope, createdBy, tags = [], options = {} }) { let extraFilter = []; // if user cannot see all, filter to private + public + departments user is in diff --git a/apps/meteor/ee/server/services/package.json b/apps/meteor/ee/server/services/package.json index 6c8b8e1f9efe..cb4e624ab7f2 100644 --- a/apps/meteor/ee/server/services/package.json +++ b/apps/meteor/ee/server/services/package.json @@ -54,12 +54,12 @@ "@types/ejson": "^2.2.2", "@types/express": "^4.17.21", "@types/fibers": "^3.1.4", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "@types/ws": "^8.5.12", "npm-run-all": "^4.1.5", "pino-pretty": "^7.6.1", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../../package.json" diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 107688676fe9..b7e7aab91573 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -129,7 +129,7 @@ "@types/mkdirp": "^1.0.2", "@types/mocha": "github:whitecolor/mocha-types", "@types/moment-timezone": "^0.5.30", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "@types/node-gcm": "^1.0.5", "@types/node-rsa": "^1.1.4", "@types/nodemailer": "^6.4.15", @@ -149,7 +149,7 @@ "@types/speakeasy": "^2.0.10", "@types/strict-uri-encode": "^2.0.2", "@types/string-strip-html": "^5.0.1", - "@types/supertest": "^2.0.16", + "@types/supertest": "^6.0.2", "@types/supports-color": "~7.2.1", "@types/textarea-caret": "^3.0.3", "@types/ua-parser-js": "^0.7.39", @@ -208,11 +208,11 @@ "storybook": "^8.3.5", "stylelint": "^14.9.1", "stylelint-order": "^5.0.0", - "supertest": "^6.2.3", + "supertest": "^7.0.0", "supports-color": "~7.2.0", "template-file": "^6.0.1", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "dependencies": { "@babel/runtime": "~7.25.7", diff --git a/apps/meteor/server/email/IMAPInterceptor.ts b/apps/meteor/server/email/IMAPInterceptor.ts index c599608cb182..dd81006a39af 100644 --- a/apps/meteor/server/email/IMAPInterceptor.ts +++ b/apps/meteor/server/email/IMAPInterceptor.ts @@ -1,4 +1,5 @@ import { EventEmitter } from 'events'; +import { Readable } from 'stream'; import { EmailInbox } from '@rocket.chat/models'; import type { ImapMessage, ImapMessageBodyInfo } from 'imap'; @@ -164,7 +165,7 @@ export class IMAPInterceptor extends EventEmitter { resolve(mail); } }; - simpleParser(stream, cb); + simpleParser(new Readable().wrap(stream), cb); }); } @@ -174,7 +175,7 @@ export class IMAPInterceptor extends EventEmitter { const messagecb = (msg: ImapMessage, seqno: number) => { out.push(seqno); const bodycb = (stream: NodeJS.ReadableStream, _info: ImapMessageBodyInfo): void => { - simpleParser(stream, (_err, email) => { + simpleParser(new Readable().wrap(stream), (_err, email) => { if (this.options.rejectBeforeTS && email.date && email.date < this.options.rejectBeforeTS) { logger.error({ msg: `Rejecting email on inbox ${this.config.user}`, subject: email.subject }); return; diff --git a/apps/meteor/tests/end-to-end/api/custom-sounds.ts b/apps/meteor/tests/end-to-end/api/custom-sounds.ts index 53d167129dd2..d21cabd7f14b 100644 --- a/apps/meteor/tests/end-to-end/api/custom-sounds.ts +++ b/apps/meteor/tests/end-to-end/api/custom-sounds.ts @@ -48,7 +48,7 @@ describe('[CustomSounds]', () => { const fileName = `test-file-${randomUUID()}`; let fileId: string; let fileId2: string; - let uploadDate: unknown; + let uploadDate: string | undefined; before((done) => getCredentials(done)); diff --git a/apps/meteor/tests/end-to-end/api/emoji-custom.ts b/apps/meteor/tests/end-to-end/api/emoji-custom.ts index c87a83783e0e..091b24452446 100644 --- a/apps/meteor/tests/end-to-end/api/emoji-custom.ts +++ b/apps/meteor/tests/end-to-end/api/emoji-custom.ts @@ -301,7 +301,7 @@ describe('[EmojiCustom]', () => { }); describe('Accessing custom emojis', () => { - let uploadDate: unknown; + let uploadDate: string | undefined; it('should return forbidden if the there is no fileId on the url', (done) => { void request diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index e5a62137d42c..0a1fb1a08286 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -2693,13 +2693,7 @@ describe('[Rooms]', () => { testUserCreds = await login(user.username, password); }); - const uploadFile = async ({ - roomId, - file, - }: { - roomId: IRoom['_id']; - file: Blob | Buffer | fs.ReadStream | string | boolean | number; - }) => { + const uploadFile = async ({ roomId, file }: { roomId: IRoom['_id']; file: Buffer | fs.ReadStream | string | boolean | number }) => { const { body } = await request .post(api(`rooms.upload/${roomId}`)) .set(credentials) diff --git a/apps/meteor/tests/end-to-end/teardown.ts b/apps/meteor/tests/end-to-end/teardown.ts index 7ad3917a985c..7ef526fd0855 100644 --- a/apps/meteor/tests/end-to-end/teardown.ts +++ b/apps/meteor/tests/end-to-end/teardown.ts @@ -11,10 +11,10 @@ let lastResponse: Response; methods.forEach((method) => { const original = request[method]; - request[method] = function (url, fn) { + request[method] = function (url) { lastUrl = url; lastMethod = method; - return original(url, fn).expect((res) => { + return original(url).expect((res) => { lastResponse = res; }); }; diff --git a/apps/uikit-playground/package.json b/apps/uikit-playground/package.json index f7e649d8b2b3..97981b45ffc5 100644 --- a/apps/uikit-playground/package.json +++ b/apps/uikit-playground/package.json @@ -51,7 +51,7 @@ "eslint": "~8.45.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.12", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "vite": "^4.5.5" }, "volta": { diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json index c88c6292957f..bdc038a7aa76 100644 --- a/ee/apps/account-service/package.json +++ b/ee/apps/account-service/package.json @@ -25,7 +25,7 @@ "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tools": "workspace:^", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "bcrypt": "^5.0.1", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", @@ -46,7 +46,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/account-service/src/service.js", "files": [ diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json index 1e1aae2fa34e..1878602ca455 100644 --- a/ee/apps/authorization-service/package.json +++ b/ee/apps/authorization-service/package.json @@ -24,7 +24,7 @@ "@rocket.chat/rest-typings": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", @@ -42,7 +42,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/authorization-service/src/service.js", "files": [ diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json index e2ee7251513d..25d13149a7e7 100644 --- a/ee/apps/ddp-streamer/package.json +++ b/ee/apps/ddp-streamer/package.json @@ -50,7 +50,7 @@ "@types/ejson": "^2.2.2", "@types/gc-stats": "^1.4.3", "@types/meteor": "^2.9.8", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "@types/polka": "^0.5.7", "@types/sharp": "^0.30.5", "@types/uuid": "^8.3.4", @@ -58,7 +58,7 @@ "eslint": "~8.45.0", "pino-pretty": "^7.6.1", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/service.js", "files": [ diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index 61c86a741610..0e731cd8b937 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -27,7 +27,7 @@ "@rocket.chat/pdf-worker": "workspace:^", "@rocket.chat/tools": "workspace:^", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", "event-loop-stats": "^1.4.1", @@ -48,7 +48,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/omnichannel-transcript/src/service.js", "files": [ diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json index 2b3b08de5078..0c016ec48df6 100644 --- a/ee/apps/presence-service/package.json +++ b/ee/apps/presence-service/package.json @@ -24,7 +24,7 @@ "@rocket.chat/presence": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", @@ -42,7 +42,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/presence-service/src/service.js", "files": [ diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index 5c3e91f2b8ac..10a51653e7da 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -24,7 +24,7 @@ "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/omnichannel-services": "workspace:^", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", "event-loop-stats": "^1.4.1", @@ -45,7 +45,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/queue-worker/src/service.js", "files": [ diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json index 601608c6d6d8..e29838e41d8f 100644 --- a/ee/apps/stream-hub-service/package.json +++ b/ee/apps/stream-hub-service/package.json @@ -24,7 +24,7 @@ "@rocket.chat/network-broker": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tracing": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "ejson": "^2.2.3", "event-loop-stats": "^1.4.1", "eventemitter3": "^4.0.7", @@ -44,7 +44,7 @@ "@types/polka": "^0.5.7", "eslint": "~8.45.0", "ts-node": "^10.9.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./dist/ee/apps/stream-hub-service/src/service.js", "files": [ diff --git a/ee/packages/license/package.json b/ee/packages/license/package.json index f2423e523aa5..22711a9a89ab 100644 --- a/ee/packages/license/package.json +++ b/ee/packages/license/package.json @@ -10,7 +10,7 @@ "eslint": "~8.45.0", "jest": "~29.7.0", "jest-websocket-mock": "~2.5.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "tsc", diff --git a/ee/packages/network-broker/package.json b/ee/packages/network-broker/package.json index 2c26090e1eea..3a50e61c0e59 100644 --- a/ee/packages/network-broker/package.json +++ b/ee/packages/network-broker/package.json @@ -6,13 +6,13 @@ "@rocket.chat/eslint-config": "workspace:^", "@types/chai": "~4.3.19", "@types/ejson": "^2.2.2", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "@types/sinon": "^10.0.20", "chai": "^4.3.10", "eslint": "~8.45.0", "jest": "~29.7.0", "sinon": "^19.0.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint src", diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json index 33f7657fcde5..845e6c16d3ce 100644 --- a/ee/packages/omnichannel-services/package.json +++ b/ee/packages/omnichannel-services/package.json @@ -8,7 +8,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "dependencies": { "@rocket.chat/core-services": "workspace:^", @@ -21,7 +21,7 @@ "@rocket.chat/rest-typings": "workspace:^", "@rocket.chat/string-helpers": "~0.31.25", "@rocket.chat/tools": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "date-fns": "^2.28.0", "ejson": "^2.2.3", "emoji-toolkit": "^7.0.1", diff --git a/ee/packages/pdf-worker/package.json b/ee/packages/pdf-worker/package.json index 8d79a199464f..a5c64d0e1ec0 100644 --- a/ee/packages/pdf-worker/package.json +++ b/ee/packages/pdf-worker/package.json @@ -43,7 +43,7 @@ "jest": "~29.7.0", "react-dom": "~18.3.1", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../../package.json" diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json index cc20990d0873..ab2f61dfda96 100644 --- a/ee/packages/presence/package.json +++ b/ee/packages/presence/package.json @@ -9,11 +9,11 @@ "@rocket.chat/apps-engine": "workspace:^", "@rocket.chat/eslint-config": "workspace:^", "@rocket.chat/rest-typings": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "babel-jest": "^29.0.3", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint src", diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json index 0495a92ea034..cf768244d706 100644 --- a/ee/packages/ui-theming/package.json +++ b/ee/packages/ui-theming/package.json @@ -16,7 +16,7 @@ "eslint-plugin-testing-library": "^5.11.1", "react": "~17.0.2", "react-docgen-typescript-plugin": "~1.0.8", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/account-utils/package.json b/packages/account-utils/package.json index 4ec6cd3bcab5..d6eaae65be89 100644 --- a/packages/account-utils/package.json +++ b/packages/account-utils/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/agenda/package.json b/packages/agenda/package.json index da3b9ea31670..1bcaef3dd5e8 100644 --- a/packages/agenda/package.json +++ b/packages/agenda/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/api-client/package.json b/packages/api-client/package.json index 929a1bb520be..2201b3a1110e 100644 --- a/packages/api-client/package.json +++ b/packages/api-client/package.json @@ -8,7 +8,7 @@ "eslint": "~8.45.0", "jest": "~29.7.0", "jest-fetch-mock": "^3.0.3", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "tsc", diff --git a/packages/apps-engine/package.json b/packages/apps-engine/package.json index f277b36704a0..c46c255ae56c 100644 --- a/packages/apps-engine/package.json +++ b/packages/apps-engine/package.json @@ -73,7 +73,7 @@ "@types/debug": "^4.1.12", "@types/lodash.clonedeep": "^4.5.7", "@types/nedb": "^1.8.12", - "@types/node": "^18.0.0", + "@types/node": "~20.16.12", "@types/semver": "^5.5.0", "@types/stack-trace": "0.0.29", "@types/uuid": "~8.3.4", diff --git a/packages/apps/package.json b/packages/apps/package.json index 3e3e280b1006..5258305c2183 100644 --- a/packages/apps/package.json +++ b/packages/apps/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/base64/package.json b/packages/base64/package.json index 4c135b864254..2e6c2ab9d002 100644 --- a/packages/base64/package.json +++ b/packages/base64/package.json @@ -21,7 +21,7 @@ "@typescript-eslint/parser": "~5.60.1", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../package.json" diff --git a/packages/cas-validate/package.json b/packages/cas-validate/package.json index a7cccb911781..b606e0812951 100644 --- a/packages/cas-validate/package.json +++ b/packages/cas-validate/package.json @@ -5,7 +5,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/core-services/package.json b/packages/core-services/package.json index e9487a8b3c72..5b65fa18252e 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -15,7 +15,7 @@ "jest": "~29.7.0", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", "prettier": "~2.8.8", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index 5efbc53e2602..1fa91be75a5e 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -9,7 +9,7 @@ "eslint": "~8.45.0", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", "prettier": "~2.8.8", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/cron/package.json b/packages/cron/package.json index 473506a13533..91162850241e 100644 --- a/packages/cron/package.json +++ b/packages/cron/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/ddp-client/package.json b/packages/ddp-client/package.json index 928a4a75bbc1..e4d3f9935101 100644 --- a/packages/ddp-client/package.json +++ b/packages/ddp-client/package.json @@ -8,7 +8,7 @@ "eslint": "~8.45.0", "jest": "~29.7.0", "jest-websocket-mock": "~2.5.0", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "ws": "^8.18.0" }, "peerDependencies": { diff --git a/packages/favicon/package.json b/packages/favicon/package.json index 67f5c6dcdfea..5a45f82bae92 100644 --- a/packages/favicon/package.json +++ b/packages/favicon/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index d9b716238c56..7acdf62edda3 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -90,7 +90,7 @@ "rimraf": "^6.0.1", "storybook": "^8.3.5", "storybook-dark-mode": "^4.0.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@rocket.chat/apps-engine": "*", diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index c7d11418084c..58dd27b9cb75 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -66,7 +66,7 @@ "outdent": "^0.8.0", "react-dom": "~17.0.2", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 41df22d139b8..c4b284c443d8 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -7,7 +7,7 @@ "eslint": "~8.45.0", "jest": "~29.7.0", "tsup": "^6.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "node ./src/index.mjs", diff --git a/packages/i18n/src/index.mjs b/packages/i18n/src/index.mjs index bfabf1f2ee41..6bf97a14322f 100644 --- a/packages/i18n/src/index.mjs +++ b/packages/i18n/src/index.mjs @@ -94,7 +94,7 @@ const dict: { export type RocketchatI18nKeys = keyof RocketchatI18n; -export = dict; +export default dict; `; const languages = files.map((file) => path.basename(file, '.i18n.json')); diff --git a/packages/instance-status/package.json b/packages/instance-status/package.json index 9afd2d88dc0d..8dc08a037184 100644 --- a/packages/instance-status/package.json +++ b/packages/instance-status/package.json @@ -7,7 +7,7 @@ "eslint": "~8.45.0", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", "prettier": "~2.8.8", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/jest-presets/package.json b/packages/jest-presets/package.json index 64a511b3de5c..f0809089d248 100644 --- a/packages/jest-presets/package.json +++ b/packages/jest-presets/package.json @@ -30,7 +30,7 @@ "@types/uuid": "^9.0.8", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../package.json" diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 59b944b90e36..ccc38eb48a98 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -7,7 +7,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "rm -rf dist && tsc", diff --git a/packages/livechat/package.json b/packages/livechat/package.json index 740760ef1723..152c993b3700 100644 --- a/packages/livechat/package.json +++ b/packages/livechat/package.json @@ -88,7 +88,7 @@ "stylelint-order": "^5.0.0", "svg-loader": "^0.0.2", "terser-webpack-plugin": "~4.2.3", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "url-loader": "^4.1.1", "webpack": "~5.95.0", "webpack-bundle-analyzer": "^4.9.1", diff --git a/packages/log-format/package.json b/packages/log-format/package.json index a999c7d884f0..6ba2282c02c3 100644 --- a/packages/log-format/package.json +++ b/packages/log-format/package.json @@ -6,7 +6,7 @@ "@types/chalk": "^2.2.0", "@types/ejson": "^2.2.2", "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/logger/package.json b/packages/logger/package.json index d073ebddbf49..8f0d1a9a7738 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/message-parser/package.json b/packages/message-parser/package.json index 3cd15485356a..c171baa00fa9 100644 --- a/packages/message-parser/package.json +++ b/packages/message-parser/package.json @@ -56,7 +56,7 @@ "@rocket.chat/peggy-loader": "workspace:~", "@rocket.chat/prettier-config": "~0.31.25", "@types/jest": "~29.5.13", - "@types/node": "~14.18.63", + "@types/node": "~20.16.12", "@typescript-eslint/parser": "~5.58.0", "babel-loader": "~9.1.3", "eslint": "~8.45.0", @@ -68,7 +68,7 @@ "rimraf": "^6.0.1", "ts-loader": "~9.4.4", "typedoc": "~0.24.8", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "webpack": "~5.95.0", "webpack-cli": "~5.1.4" }, diff --git a/packages/mock-providers/package.json b/packages/mock-providers/package.json index 83b90405544e..0aeae959bfa6 100644 --- a/packages/mock-providers/package.json +++ b/packages/mock-providers/package.json @@ -17,7 +17,7 @@ "@types/use-sync-external-store": "^0.0.6", "eslint": "~8.45.0", "react": "~17.0.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@tanstack/react-query": "*", diff --git a/packages/model-typings/package.json b/packages/model-typings/package.json index 936abcb632fb..65d306dc982a 100644 --- a/packages/model-typings/package.json +++ b/packages/model-typings/package.json @@ -6,7 +6,7 @@ "@types/node-rsa": "^1.1.4", "eslint": "~8.45.0", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/models/package.json b/packages/models/package.json index 70f290f5aa1e..4622e30f93be 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -7,7 +7,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "^29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "dependencies": { "@rocket.chat/model-typings": "workspace:~" diff --git a/packages/node-poplib/package.json b/packages/node-poplib/package.json index 1d0c4c462e46..12b982071f91 100644 --- a/packages/node-poplib/package.json +++ b/packages/node-poplib/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "main": "./src/index.js", "typings": "./dist/index.d.ts" diff --git a/packages/password-policies/package.json b/packages/password-policies/package.json index 6bfb0d6100f1..ecde88b09a5e 100644 --- a/packages/password-policies/package.json +++ b/packages/password-policies/package.json @@ -7,7 +7,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "rm -rf dist && tsc", diff --git a/packages/patch-injection/package.json b/packages/patch-injection/package.json index 2ac35749c481..895f9d5d3c38 100644 --- a/packages/patch-injection/package.json +++ b/packages/patch-injection/package.json @@ -7,7 +7,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "rm -rf dist && tsc", diff --git a/packages/peggy-loader/package.json b/packages/peggy-loader/package.json index ee873e4a8e41..4a26ad60237a 100644 --- a/packages/peggy-loader/package.json +++ b/packages/peggy-loader/package.json @@ -44,13 +44,13 @@ "devDependencies": { "@rocket.chat/eslint-config": "workspace:~", "@rocket.chat/prettier-config": "~0.31.25", - "@types/node": "~14.18.63", + "@types/node": "~20.16.12", "eslint": "~8.45.0", "npm-run-all": "^4.1.5", "peggy": "3.0.2", "prettier": "~2.8.8", "rimraf": "^6.0.1", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "webpack": "~5.95.0" }, "volta": { diff --git a/packages/random/package.json b/packages/random/package.json index db0d71da86b9..ce97498e087a 100644 --- a/packages/random/package.json +++ b/packages/random/package.json @@ -23,7 +23,7 @@ "@typescript-eslint/parser": "~5.60.1", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../package.json" diff --git a/packages/release-action/package.json b/packages/release-action/package.json index 73f926ce5160..a1d4db2f3842 100644 --- a/packages/release-action/package.json +++ b/packages/release-action/package.json @@ -10,8 +10,8 @@ "main": "dist/index.js", "packageManager": "yarn@3.5.1", "devDependencies": { - "@types/node": "^16.18.108", - "typescript": "~5.5.4" + "@types/node": "~20.16.12", + "typescript": "~5.6.3" }, "dependencies": { "@actions/core": "^1.10.1", diff --git a/packages/release-changelog/package.json b/packages/release-changelog/package.json index 222f7f20355a..3f7f5c2fed4d 100644 --- a/packages/release-changelog/package.json +++ b/packages/release-changelog/package.json @@ -10,9 +10,9 @@ "devDependencies": { "@changesets/types": "^6.0.0", "@rocket.chat/eslint-config": "workspace:^", - "@types/node": "^14.18.63", + "@types/node": "~20.16.12", "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "dependencies": { "dataloader": "^1.4.0", diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json index 83f4632d1433..35d4cd1a6bea 100644 --- a/packages/rest-typings/package.json +++ b/packages/rest-typings/package.json @@ -8,7 +8,7 @@ "eslint": "~8.45.0", "jest": "~29.7.0", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "build": "rm -rf dist && tsc", diff --git a/packages/server-cloud-communication/package.json b/packages/server-cloud-communication/package.json index 35e67a2c0638..b78345e36773 100644 --- a/packages/server-cloud-communication/package.json +++ b/packages/server-cloud-communication/package.json @@ -5,7 +5,7 @@ "devDependencies": { "@rocket.chat/license": "workspace:^", "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../package.json" diff --git a/packages/server-fetch/package.json b/packages/server-fetch/package.json index cceb6dd40137..46ef65d3a7ba 100644 --- a/packages/server-fetch/package.json +++ b/packages/server-fetch/package.json @@ -5,7 +5,7 @@ "devDependencies": { "@types/node-fetch": "~2.6.11", "eslint": "~8.45.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/sha256/package.json b/packages/sha256/package.json index 1ce6712489fe..f10be0d72404 100644 --- a/packages/sha256/package.json +++ b/packages/sha256/package.json @@ -22,7 +22,7 @@ "@typescript-eslint/parser": "~5.60.1", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "volta": { "extends": "../../package.json" diff --git a/packages/tools/package.json b/packages/tools/package.json index c10fccdb5030..23555ff93b57 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -7,7 +7,7 @@ "@types/jest": "~29.5.13", "eslint": "~8.45.0", "jest": "~29.7.0", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/ui-avatar/package.json b/packages/ui-avatar/package.json index 15d947c6a819..0835ba8c583e 100644 --- a/packages/ui-avatar/package.json +++ b/packages/ui-avatar/package.json @@ -14,7 +14,7 @@ "eslint-plugin-storybook": "~0.6.15", "eslint-plugin-testing-library": "~5.11.1", "react": "^17.0.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index 72ea44034243..4daf5a52846e 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -52,7 +52,7 @@ "react-dom": "^17.0.2", "react-hook-form": "~7.45.4", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@react-aria/toolbar": "*", diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index 35549b7df460..721cae9fb4c9 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -39,7 +39,7 @@ "react": "~17.0.2", "react-dom": "~17.0.2", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@react-aria/toolbar": "*", diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index 0e29ba67d05e..96b8aa8d50ff 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -16,7 +16,7 @@ "eslint-plugin-react-hooks": "^4.6.2", "mongodb": "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch", "react": "~17.0.2", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "use-sync-external-store": "^1.2.2" }, "peerDependencies": { diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index 8fb2e9a84c9e..06653dd1941d 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -53,7 +53,7 @@ "ts-loader": "~9.4.4", "ts-node": "~10.9.2", "ts-patch": "~3.2.1", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "dependencies": { "typia": "~6.9.0" diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index f26e43571606..ca5c9c2e3377 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -50,7 +50,7 @@ "jest-axe": "~9.0.0", "react": "~17.0.2", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@rocket.chat/css-in-js": "*", diff --git a/packages/ui-voip/package.json b/packages/ui-voip/package.json index 307da263043a..16f92c5aa25d 100644 --- a/packages/ui-voip/package.json +++ b/packages/ui-voip/package.json @@ -57,7 +57,7 @@ "jest-axe": "~9.0.0", "react": "~17.0.2", "storybook": "^8.3.5", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@rocket.chat/css-in-js": "*", diff --git a/packages/web-ui-registration/package.json b/packages/web-ui-registration/package.json index 958f01cc7aab..9eb5574ea391 100644 --- a/packages/web-ui-registration/package.json +++ b/packages/web-ui-registration/package.json @@ -44,7 +44,7 @@ "react-i18next": "~13.2.2", "storybook": "^8.3.5", "storybook-dark-mode": "^4.0.2", - "typescript": "~5.5.4" + "typescript": "~5.6.3" }, "peerDependencies": { "@rocket.chat/layout": "*", diff --git a/yarn.lock b/yarn.lock index 7f22ffc44c08..19d9ff0d5bb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8030,7 +8030,7 @@ __metadata: "@rocket.chat/tracing": "workspace:^" "@types/bcrypt": "npm:^5.0.2" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" bcrypt: "npm:^5.0.1" ejson: "npm:^2.2.3" @@ -8045,7 +8045,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" uuid: "npm:^9.0.1" languageName: unknown linkType: soft @@ -8055,7 +8055,7 @@ __metadata: resolution: "@rocket.chat/account-utils@workspace:packages/account-utils" dependencies: eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8071,7 +8071,7 @@ __metadata: human-interval: "npm:^2.0.1" moment-timezone: "npm:~0.5.46" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8091,7 +8091,7 @@ __metadata: query-string: "npm:^7.1.3" split-on-first: "npm:^3.0.0" strict-uri-encode: "npm:^2.0.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8106,7 +8106,7 @@ __metadata: "@types/debug": "npm:^4.1.12" "@types/lodash.clonedeep": "npm:^4.5.7" "@types/nedb": "npm:^1.8.12" - "@types/node": "npm:^18.0.0" + "@types/node": "npm:~20.16.12" "@types/semver": "npm:^5.5.0" "@types/stack-trace": "npm:0.0.29" "@types/uuid": "npm:~8.3.4" @@ -8146,7 +8146,7 @@ __metadata: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/model-typings": "workspace:^" eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8165,7 +8165,7 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" @@ -8179,7 +8179,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8195,7 +8195,7 @@ __metadata: "@typescript-eslint/parser": "npm:~5.60.1" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8205,7 +8205,7 @@ __metadata: dependencies: cheerio: "npm:1.0.0-rc.10" eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8232,7 +8232,7 @@ __metadata: jest: "npm:~29.7.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" prettier: "npm:~2.8.8" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8249,7 +8249,7 @@ __metadata: eslint: "npm:~8.45.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" prettier: "npm:~2.8.8" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8263,7 +8263,7 @@ __metadata: "@rocket.chat/random": "workspace:^" eslint: "npm:~8.45.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8302,7 +8302,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" jest-websocket-mock: "npm:~2.5.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" ws: "npm:^8.18.0" peerDependencies: "@rocket.chat/emitter": "*" @@ -8330,7 +8330,7 @@ __metadata: "@types/ejson": "npm:^2.2.2" "@types/gc-stats": "npm:^1.4.3" "@types/meteor": "npm:^2.9.8" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" "@types/sharp": "npm:^0.30.5" "@types/uuid": "npm:^8.3.4" @@ -8351,7 +8351,7 @@ __metadata: polka: "npm:^0.5.2" sharp: "npm:^0.32.6" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" underscore: "npm:^1.13.7" uuid: "npm:^7.0.3" ws: "npm:^8.18.0" @@ -8391,7 +8391,7 @@ __metadata: resolution: "@rocket.chat/favicon@workspace:packages/favicon" dependencies: eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8510,7 +8510,7 @@ __metadata: rimraf: "npm:^6.0.1" storybook: "npm:^8.3.5" storybook-dark-mode: "npm:^4.0.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/apps-engine": "*" "@rocket.chat/eslint-config": 0.7.0 @@ -8601,7 +8601,7 @@ __metadata: react-dom: "npm:~17.0.2" react-error-boundary: "npm:^3.1.4" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": "*" @@ -8624,7 +8624,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" tsup: "npm:^6.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8645,7 +8645,7 @@ __metadata: eslint: "npm:~8.45.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" prettier: "npm:~2.8.8" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8667,7 +8667,7 @@ __metadata: jest-axe: "npm:~9.0.0" jest-environment-jsdom: "npm:~29.7.0" jest-environment-node: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" uuid: "npm:~9.0.1" languageName: unknown linkType: soft @@ -8681,7 +8681,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" jose: "npm:^4.15.9" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8712,7 +8712,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" jest-websocket-mock: "npm:~2.5.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8803,7 +8803,7 @@ __metadata: stylelint-order: "npm:^5.0.0" svg-loader: "npm:^0.0.2" terser-webpack-plugin: "npm:~4.2.3" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" url-loader: "npm:^4.1.1" webpack: "npm:~5.95.0" webpack-bundle-analyzer: "npm:^4.9.1" @@ -8825,7 +8825,7 @@ __metadata: chalk: "npm:^4.0.0" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8836,7 +8836,7 @@ __metadata: "@rocket.chat/emitter": "npm:~0.31.25" eslint: "npm:~8.45.0" pino: "npm:^8.15.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8872,7 +8872,7 @@ __metadata: "@rocket.chat/peggy-loader": "workspace:~" "@rocket.chat/prettier-config": "npm:~0.31.25" "@types/jest": "npm:~29.5.13" - "@types/node": "npm:~14.18.63" + "@types/node": "npm:~20.16.12" "@typescript-eslint/parser": "npm:~5.58.0" babel-loader: "npm:~9.1.3" eslint: "npm:~8.45.0" @@ -8885,7 +8885,7 @@ __metadata: tldts: "npm:~5.7.112" ts-loader: "npm:~9.4.4" typedoc: "npm:~0.24.8" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" webpack: "npm:~5.95.0" webpack-cli: "npm:~5.1.4" languageName: unknown @@ -9040,7 +9040,7 @@ __metadata: "@types/mkdirp": "npm:^1.0.2" "@types/mocha": "github:whitecolor/mocha-types" "@types/moment-timezone": "npm:^0.5.30" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/node-gcm": "npm:^1.0.5" "@types/node-rsa": "npm:^1.1.4" "@types/nodemailer": "npm:^6.4.15" @@ -9060,7 +9060,7 @@ __metadata: "@types/speakeasy": "npm:^2.0.10" "@types/strict-uri-encode": "npm:^2.0.2" "@types/string-strip-html": "npm:^5.0.1" - "@types/supertest": "npm:^2.0.16" + "@types/supertest": "npm:^6.0.2" "@types/supports-color": "npm:~7.2.1" "@types/textarea-caret": "npm:^3.0.3" "@types/ua-parser-js": "npm:^0.7.39" @@ -9249,7 +9249,7 @@ __metadata: string-strip-html: "npm:^7.0.3" stylelint: "npm:^14.9.1" stylelint-order: "npm:^5.0.0" - supertest: "npm:^6.2.3" + supertest: "npm:^7.0.0" supports-color: "npm:~7.2.0" suretype: "npm:~2.4.1" swiper: "npm:^9.4.1" @@ -9259,7 +9259,7 @@ __metadata: ts-node: "npm:^10.9.2" twilio: "npm:^3.76.1" twit: "npm:^2.2.11" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" typia: "npm:~6.9.0" ua-parser-js: "npm:^1.0.39" underscore: "npm:^1.13.7" @@ -9291,7 +9291,7 @@ __metadata: i18next: "npm:~23.4.9" react: "npm:~17.0.2" react-i18next: "npm:~13.2.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" use-sync-external-store: "npm:~1.2.2" peerDependencies: "@tanstack/react-query": "*" @@ -9307,7 +9307,7 @@ __metadata: "@types/node-rsa": "npm:^1.1.4" eslint: "npm:~8.45.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9320,7 +9320,7 @@ __metadata: "@types/jest": "npm:~29.5.13" eslint: "npm:~8.45.0" jest: "npm:^29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9341,7 +9341,7 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@types/chai": "npm:~4.3.19" "@types/ejson": "npm:^2.2.2" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/sinon": "npm:^10.0.20" chai: "npm:^4.3.10" ejson: "npm:^2.2.3" @@ -9350,7 +9350,7 @@ __metadata: moleculer: "npm:^0.14.34" pino: "npm:^8.15.0" sinon: "npm:^19.0.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9371,7 +9371,7 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tools": "workspace:^" "@types/jest": "npm:~29.5.13" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" date-fns: "npm:^2.28.0" ejson: "npm:^2.2.3" emoji-toolkit: "npm:^7.0.1" @@ -9383,7 +9383,7 @@ __metadata: mongo-message-queue: "npm:^1.0.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" pino: "npm:^8.15.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9405,7 +9405,7 @@ __metadata: "@rocket.chat/tools": "workspace:^" "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" ejson: "npm:^2.2.3" emoji-toolkit: "npm:^7.0.1" @@ -9422,7 +9422,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9455,7 +9455,7 @@ __metadata: "@types/jest": "npm:~29.5.13" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9467,7 +9467,7 @@ __metadata: "@types/jest": "npm:~29.5.13" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9498,7 +9498,7 @@ __metadata: react: "npm:~18.3.1" react-dom: "npm:~18.3.1" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9508,13 +9508,13 @@ __metadata: dependencies: "@rocket.chat/eslint-config": "workspace:~" "@rocket.chat/prettier-config": "npm:~0.31.25" - "@types/node": "npm:~14.18.63" + "@types/node": "npm:~20.16.12" eslint: "npm:~8.45.0" npm-run-all: "npm:^4.1.5" peggy: "npm:3.0.2" prettier: "npm:~2.8.8" rimraf: "npm:^6.0.1" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" webpack: "npm:~5.95.0" peerDependencies: peggy: "*" @@ -9527,7 +9527,7 @@ __metadata: resolution: "@rocket.chat/poplib@workspace:packages/node-poplib" dependencies: eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9546,7 +9546,7 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" @@ -9560,7 +9560,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9577,12 +9577,12 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/models": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" babel-jest: "npm:^29.0.3" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9610,7 +9610,7 @@ __metadata: "@rocket.chat/omnichannel-services": "workspace:^" "@rocket.chat/tracing": "workspace:^" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" ejson: "npm:^2.2.3" emoji-toolkit: "npm:^7.0.1" @@ -9627,7 +9627,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9643,7 +9643,7 @@ __metadata: "@typescript-eslint/parser": "npm:~5.60.1" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9656,13 +9656,13 @@ __metadata: "@actions/github": "npm:^5.1.1" "@octokit/plugin-throttling": "npm:^6.0.0" "@rocket.chat/eslint-config": "workspace:^" - "@types/node": "npm:^16.18.108" + "@types/node": "npm:~20.16.12" eslint: "npm:~8.45.0" mdast-util-to-string: "npm:2.0.0" remark-parse: "npm:9.0.0" remark-stringify: "npm:9.0.1" semver: "npm:^7.6.3" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" unified: "npm:9.2.2" languageName: unknown linkType: soft @@ -9673,11 +9673,11 @@ __metadata: dependencies: "@changesets/types": "npm:^6.0.0" "@rocket.chat/eslint-config": "workspace:^" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" dataloader: "npm:^1.4.0" eslint: "npm:~8.45.0" node-fetch: "npm:^2.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9696,7 +9696,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9719,7 +9719,7 @@ __metadata: dependencies: "@rocket.chat/license": "workspace:^" eslint: "npm:~8.45.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9734,7 +9734,7 @@ __metadata: https-proxy-agent: "npm:^5.0.1" node-fetch: "npm:2.7.0" proxy-from-env: "npm:^1.1.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9750,7 +9750,7 @@ __metadata: "@typescript-eslint/parser": "npm:~5.60.1" eslint: "npm:~8.45.0" jest: "npm:~29.7.0" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9771,7 +9771,7 @@ __metadata: "@rocket.chat/tracing": "workspace:^" "@types/bcrypt": "npm:^5.0.2" "@types/gc-stats": "npm:^1.4.3" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/polka": "npm:^0.5.7" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" @@ -9785,7 +9785,7 @@ __metadata: pino: "npm:^8.15.0" polka: "npm:^0.5.2" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9825,7 +9825,7 @@ __metadata: eslint: "npm:~8.45.0" jest: "npm:~29.7.0" moment-timezone: "npm:^0.5.46" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -9859,7 +9859,7 @@ __metadata: eslint-plugin-storybook: "npm:~0.6.15" eslint-plugin-testing-library: "npm:~5.11.1" react: "npm:^17.0.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/ui-contexts": 11.0.0 @@ -9905,7 +9905,7 @@ __metadata: react-dom: "npm:^17.0.2" react-hook-form: "npm:~7.45.4" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@react-aria/toolbar": "*" "@rocket.chat/css-in-js": "*" @@ -9944,7 +9944,7 @@ __metadata: react: "npm:~17.0.2" react-dom: "npm:~17.0.2" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@react-aria/toolbar": "*" "@rocket.chat/fuselage": "*" @@ -9972,7 +9972,7 @@ __metadata: eslint-plugin-react-hooks: "npm:^4.6.2" mongodb: "patch:mongodb@npm%3A4.17.2#~/.yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch" react: "npm:~17.0.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" use-sync-external-store: "npm:^1.2.2" peerDependencies: "@rocket.chat/core-typings": "workspace:^" @@ -10008,7 +10008,7 @@ __metadata: ts-loader: "npm:~9.4.4" ts-node: "npm:~10.9.2" ts-patch: "npm:~3.2.1" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" typia: "npm:~6.9.0" peerDependencies: "@rocket.chat/icons": "*" @@ -10032,7 +10032,7 @@ __metadata: eslint-plugin-testing-library: "npm:^5.11.1" react: "npm:~17.0.2" react-docgen-typescript-plugin: "npm:~1.0.8" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" @@ -10076,7 +10076,7 @@ __metadata: jest-axe: "npm:~9.0.0" react: "npm:~17.0.2" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" @@ -10131,7 +10131,7 @@ __metadata: react-i18next: "npm:~13.2.2" sip.js: "npm:^0.20.1" storybook: "npm:^8.3.5" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" @@ -10188,7 +10188,7 @@ __metadata: react-split-pane: "npm:^0.1.92" react-virtuoso: "npm:^4.7.1" reactflow: "npm:^11.7.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" use-subscription: "npm:^1.8.2" vite: "npm:^4.5.5" languageName: unknown @@ -10225,7 +10225,7 @@ __metadata: react-i18next: "npm:~13.2.2" storybook: "npm:^8.3.5" storybook-dark-mode: "npm:^4.0.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.2 @@ -11644,10 +11644,10 @@ __metadata: languageName: node linkType: hard -"@types/cookiejar@npm:*": - version: 2.1.2 - resolution: "@types/cookiejar@npm:2.1.2" - checksum: 10/f6e1903454007f86edd6c3520cbb4d553e1d4e17eaf1f77f6f75e3270f48cc828d74397a113a36942f5fe52f9fa71067bcfa738f53ad468fcca0bc52cb1cbd28 +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10/04d5990e87b6387532d15a87d9ec9b2eb783039291193863751dcfd7fc723a3b3aa30ce4c06b03975cba58632e933772f1ff031af23eaa3ac7f94e71afa6e073 languageName: node linkType: hard @@ -12609,6 +12609,13 @@ __metadata: languageName: node linkType: hard +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10/ad2a7178486f2fd167750f3eb920ab032a947ff2e26f55c86670a6038632d790b46f52e5b6ead5823f1e53fc68028f1e9ddd15cfead7903e04517c88debd72b1 + languageName: node + linkType: hard + "@types/mime@npm:*": version: 3.0.1 resolution: "@types/mime@npm:3.0.1" @@ -12758,26 +12765,19 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37, @types/node@npm:^14.18.63, @types/node@npm:~14.18.63": +"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37": version: 14.18.63 resolution: "@types/node@npm:14.18.63" checksum: 10/82a7775898c2ea6db0b610a463512206fb2c7adc1af482c7eb44b99d94375fff51c74f67ae75a63c5532971159f30c866a4d308000624ef02fd9a7175e277019 languageName: node linkType: hard -"@types/node@npm:^16.18.108": - version: 16.18.108 - resolution: "@types/node@npm:16.18.108" - checksum: 10/5963b628e3a2a89aa1090b1ffb3cf6a3d366ed118c8bf5ecd1e4b7fc89782fc1c53d93926886a9f2bdcf6fe5f5a6d361027cbf8eca026a20f15ffe374a1f57b3 - languageName: node - linkType: hard - -"@types/node@npm:^18.0.0": - version: 18.19.50 - resolution: "@types/node@npm:18.19.50" +"@types/node@npm:~20.16.12": + version: 20.16.12 + resolution: "@types/node@npm:20.16.12" dependencies: - undici-types: "npm:~5.26.4" - checksum: 10/d238bb877953fcecda830df140f8722b9ba9644ae63e810fe6fa40cab8285c42f9b34c9529f2144a6f8cfeee5b0ff7fefd9425261e41830157d6710d501b829d + undici-types: "npm:~6.19.2" + checksum: 10/689badb5af2a1a03553a6d21880fa4aabb8cf028b7db1a03be889c0026047a780ac37c83df5dca036f02f5dc3cc4000254fa40d2cadd5df0e9bd6f43dae6eac6 languageName: node linkType: hard @@ -13199,22 +13199,25 @@ __metadata: languageName: node linkType: hard -"@types/superagent@npm:*": - version: 4.1.15 - resolution: "@types/superagent@npm:4.1.15" +"@types/superagent@npm:^8.1.0": + version: 8.1.9 + resolution: "@types/superagent@npm:8.1.9" dependencies: - "@types/cookiejar": "npm:*" + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" "@types/node": "npm:*" - checksum: 10/32df316b028fbd49d9dbd778d9a8ec4166a5cb089a5fadee56066b868d5b2a801ac924ef2a0ce1e201dc8315f6375a1f49f5e87f81fa5219d4fe8442146e846e + form-data: "npm:^4.0.0" + checksum: 10/6d9687b0bc3d693b900ef76000b02437a70879c3219b28606879c086d786bb1e48429813e72e32dd0aafc94c053a78a2aa8be67c45bc8e6b968ca62d6d5cc554 languageName: node linkType: hard -"@types/supertest@npm:^2.0.16": - version: 2.0.16 - resolution: "@types/supertest@npm:2.0.16" +"@types/supertest@npm:^6.0.2": + version: 6.0.2 + resolution: "@types/supertest@npm:6.0.2" dependencies: - "@types/superagent": "npm:*" - checksum: 10/2fc998ea698e0467cdbe3bea0ebce2027ea3a45a13e51a6cecb0435f44b486faecf99c34d8702d2d7fe033e6e09fdd2b374af52ecc8d0c69a1deec66b8c0dd52 + "@types/methods": "npm:^1.1.4" + "@types/superagent": "npm:^8.1.0" + checksum: 10/4b67fb2d1bfbb7ff0a7dfaaf190cdf2e0014522615fb2dc53c214bdac95b4ee42696dd1df13332c90a7765cc52934c9cc0c428bf0f9e8189167aef01042e7448 languageName: node linkType: hard @@ -17748,10 +17751,10 @@ __metadata: languageName: node linkType: hard -"cookiejar@npm:^2.1.3": - version: 2.1.3 - resolution: "cookiejar@npm:2.1.3" - checksum: 10/431f1ec36e74a8f48fc0e60bb2a4e108f6bf9bc43e878e98aecb412633d3f9dd0615c9b4534c622fd19672a4deeec5bf8bf482e8e4fd527a26dfe89ee5a8486a +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10/4a184f5a0591df8b07d22a43ea5d020eacb4572c383e853a33361a99710437eaa0971716c688684075bbf695b484f5872e9e3f562382e46858716cb7fc8ce3f4 languageName: node linkType: hard @@ -19261,13 +19264,13 @@ __metadata: languageName: node linkType: hard -"dezalgo@npm:1.0.3": - version: 1.0.3 - resolution: "dezalgo@npm:1.0.3" +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" dependencies: asap: "npm:^2.0.0" wrappy: "npm:1" - checksum: 10/960f4b6230866cb61f23d113170ca3bf84210a2801f8b8e24ee2b5d40402400358c75459293cab94dae6fdfb41004aebe1c847a65fb0b2ef091bf5a35c80faad + checksum: 10/895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 languageName: node linkType: hard @@ -22139,15 +22142,14 @@ __metadata: languageName: node linkType: hard -"formidable@npm:^2.0.1": - version: 2.0.1 - resolution: "formidable@npm:2.0.1" +"formidable@npm:^3.5.1": + version: 3.5.1 + resolution: "formidable@npm:3.5.1" dependencies: - dezalgo: "npm:1.0.3" - hexoid: "npm:1.0.0" - once: "npm:1.4.0" - qs: "npm:6.9.3" - checksum: 10/f0ad9266e61b0a3ebd301fa6efbc9ea5cbdcf7ef2fbd7f9f1122c9172e41d00323615597f0f5ac6b821cda3f32a6bdf4dc8e77ca61a3124ce5dcf17d69d5954a + dezalgo: "npm:^1.0.4" + hexoid: "npm:^1.0.0" + once: "npm:^1.4.0" + checksum: 10/c9a7bbbd4ca8142893da88b51cf7797adee022344ea180cf157a108bf999bed5ad8bc07a10a28d8a39fcbfaa02e8cba07f4ba336fbeb330deb23907336ba1fc2 languageName: node linkType: hard @@ -23280,7 +23282,7 @@ __metadata: languageName: node linkType: hard -"hexoid@npm:1.0.0": +"hexoid@npm:^1.0.0": version: 1.0.0 resolution: "hexoid@npm:1.0.0" checksum: 10/f2271b8b6b0e13fb5a1eccf740f53ce8bae689c80b9498b854c447f9dc94f75f44e0de064c0e4660ecdbfa8942bb2b69973fdcb080187b45bbb409a3c71f19d4 @@ -29604,7 +29606,7 @@ __metadata: languageName: node linkType: hard -"once@npm:1.4.0, once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -32035,7 +32037,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0, qs@npm:^6.10.3, qs@npm:^6.12.3, qs@npm:^6.9.4": +"qs@npm:6.13.0, qs@npm:^6.11.0, qs@npm:^6.12.3, qs@npm:^6.9.4": version: 6.13.0 resolution: "qs@npm:6.13.0" dependencies: @@ -32044,13 +32046,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.9.3": - version: 6.9.3 - resolution: "qs@npm:6.9.3" - checksum: 10/259d06d089c3c677c40533f60b6434d168712c18d304319a7aa6d371a7bc0b029e98fe8fb2e768f0fd371f92891e4314ddedfe3f14a9b9ff5d98ef460dd8d309 - languageName: node - linkType: hard - "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -33760,7 +33755,7 @@ __metadata: "@types/ejson": "npm:^2.2.2" "@types/express": "npm:^4.17.21" "@types/fibers": "npm:^3.1.4" - "@types/node": "npm:^14.18.63" + "@types/node": "npm:~20.16.12" "@types/ws": "npm:^8.5.12" ajv: "npm:^8.11.0" bcrypt: "npm:^5.0.1" @@ -33782,7 +33777,7 @@ __metadata: sodium-native: "npm:^3.3.0" sodium-plus: "npm:^0.9.0" ts-node: "npm:^10.9.2" - typescript: "npm:~5.5.4" + typescript: "npm:~5.6.3" uuid: "npm:^8.3.2" ws: "npm:^8.18.0" languageName: unknown @@ -35869,22 +35864,20 @@ __metadata: languageName: node linkType: hard -"superagent@npm:^8.0.0": - version: 8.0.0 - resolution: "superagent@npm:8.0.0" +"superagent@npm:^9.0.1": + version: 9.0.2 + resolution: "superagent@npm:9.0.2" dependencies: component-emitter: "npm:^1.3.0" - cookiejar: "npm:^2.1.3" + cookiejar: "npm:^2.1.4" debug: "npm:^4.3.4" fast-safe-stringify: "npm:^2.1.1" form-data: "npm:^4.0.0" - formidable: "npm:^2.0.1" + formidable: "npm:^3.5.1" methods: "npm:^1.1.2" mime: "npm:2.6.0" - qs: "npm:^6.10.3" - readable-stream: "npm:^3.6.0" - semver: "npm:^7.3.7" - checksum: 10/74af05269785290683bb0b29501ad7b7554aafbf06ad37a2e677fb5b4f8c14ee7291ef685ba1bdd138d1c2dc529b551aec181c43ab4b509fdb79acbc0cfa0e38 + qs: "npm:^6.11.0" + checksum: 10/d3c0c9051ceec84d5b431eaa410ad81bcd53255cea57af1fc66d683a24c34f3ba4761b411072a9bf489a70e3d5b586a78a0e6f2eac6a561067e7d196ddab0907 languageName: node linkType: hard @@ -35897,13 +35890,13 @@ __metadata: languageName: node linkType: hard -"supertest@npm:^6.2.3": - version: 6.2.4 - resolution: "supertest@npm:6.2.4" +"supertest@npm:^7.0.0": + version: 7.0.0 + resolution: "supertest@npm:7.0.0" dependencies: methods: "npm:^1.1.2" - superagent: "npm:^8.0.0" - checksum: 10/5b184f9cb932ed962783516073245e4dc713760f4b410c05e54b7aa5a7e6e3940bfce40af1be601bb1a4d498489c58be44ca43a2c9f67e430c8355b5496f20f5 + superagent: "npm:^9.0.1" + checksum: 10/73bf2a37e13856a1b3e6a37b9df5cec8e506aa0360a5f5ecd989d1f4b0edf168883e306012e81e371d5252c17d4c7bef4ba30633dbf3877cbf52fc7af51cca9b languageName: node linkType: hard @@ -37408,13 +37401,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.5.4": - version: 5.5.4 - resolution: "typescript@npm:5.5.4" +"typescript@npm:~5.6.3": + version: 5.6.3 + resolution: "typescript@npm:5.6.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/1689ccafef894825481fc3d856b4834ba3cc185a9c2878f3c76a9a1ef81af04194849840f3c69e7961e2312771471bb3b460ca92561e1d87599b26c37d0ffb6f + checksum: 10/c328e418e124b500908781d9f7b9b93cf08b66bf5936d94332b463822eea2f4e62973bfb3b8a745fdc038785cb66cf59d1092bac3ec2ac6a3e5854687f7833f1 languageName: node linkType: hard @@ -37438,13 +37431,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.5.4#optional!builtin": - version: 5.5.4 - resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07" +"typescript@patch:typescript@npm%3A~5.6.3#optional!builtin": + version: 5.6.3 + resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/746fdd0865c5ce4f15e494c57ede03a9e12ede59cfdb40da3a281807853fe63b00ef1c912d7222143499aa82f18b8b472baa1830df8804746d09b55f6cf5b1cc + checksum: 10/00504c01ee42d470c23495426af07512e25e6546bce7e24572e72a9ca2e6b2e9bea63de4286c3cfea644874da1467dcfca23f4f98f7caf20f8b03c0213bb6837 languageName: node linkType: hard @@ -37584,13 +37577,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10/0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd - languageName: node - linkType: hard - "undici-types@npm:~6.19.2": version: 6.19.8 resolution: "undici-types@npm:6.19.8" From 1421f25eb657281e2dabb7a7543bf410b4716bc5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Sat, 19 Oct 2024 02:25:58 -0300 Subject: [PATCH 05/12] chore: add extra log to `No broker set to broadcast` --- packages/core-services/src/lib/Api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-services/src/lib/Api.ts b/packages/core-services/src/lib/Api.ts index d02dfe1f8a56..d09b821b601e 100644 --- a/packages/core-services/src/lib/Api.ts +++ b/packages/core-services/src/lib/Api.ts @@ -47,7 +47,7 @@ export class Api implements IApiService { async broadcast(event: T, ...args: Parameters): Promise { if (!this.broker) { - throw new Error(`No broker set to broadcast: ${event}`); + throw new Error(`No broker set to broadcast: ${event}, ${JSON.stringify(args)}`); } return this.broker.broadcast(event, ...args); From 5f3ae1c249883c85deb0fca293dc93b737c9037f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Sat, 19 Oct 2024 11:20:17 -0300 Subject: [PATCH 06/12] regression: server randomly failing on startup (#33675) --- apps/meteor/ee/app/license/server/index.ts | 1 - apps/meteor/ee/app/license/server/startup.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/ee/app/license/server/index.ts b/apps/meteor/ee/app/license/server/index.ts index efef260a6cb0..9177532a9e21 100644 --- a/apps/meteor/ee/app/license/server/index.ts +++ b/apps/meteor/ee/app/license/server/index.ts @@ -1,3 +1,2 @@ import './settings'; import './methods'; -import './airGappedRestrictions'; diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index 14f88bc27d61..eb80fcaf771d 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -115,6 +115,7 @@ export const startLicense = async () => { return new Promise((resolve) => { // When settings are loaded, apply the current license if there is one. settings.onReady(async () => { + import('./airGappedRestrictions'); if (!(await applyLicense(settings.get('Enterprise_License') ?? '', false))) { // License from the envvar is always treated as new, because it would have been saved on the setting if it was already in use. if (process.env.ROCKETCHAT_LICENSE && !License.hasValidLicense()) { From 6acbadc96c22b6d87fb5f6e89429f85ccc3a9345 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Sat, 19 Oct 2024 12:25:32 -0300 Subject: [PATCH 07/12] chore: remove allowed routes for query and fields (#33622) Co-authored-by: Marcos Spessatto Defendi <15324204+MarcosSpessatto@users.noreply.github.com> --- .../app/api/server/helpers/parseJsonQuery.ts | 16 +------- apps/meteor/app/api/server/v1/users.ts | 4 +- apps/meteor/tests/end-to-end/api/rooms.ts | 17 -------- apps/meteor/tests/end-to-end/api/users.ts | 40 +++---------------- .../src/v1/users/UsersInfoParamsGet.ts | 10 +++++ 5 files changed, 18 insertions(+), 69 deletions(-) diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts index 35fe6155a396..807f72080e4b 100644 --- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts +++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts @@ -53,24 +53,12 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ } } - // TODO: Remove this once we have all routes migrated to the new API params - const hasSupportedRoutes = [ - '/api/v1/directory', - '/api/v1/channels.files', - '/api/v1/integrations.list', - '/api/v1/custom-user-status.list', - '/api/v1/custom-sounds.list', - '/api/v1/channels.list', - '/api/v1/channels.online', - '/api/v1/emoji-custom.list', - ].includes(route); - const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE'; const messageGenerator = ({ endpoint, version, parameter }: { endpoint: string; version: string; parameter: string }): string => `The usage of the "${parameter}" parameter in endpoint "${endpoint}" breaks the security of the API and can lead to data exposure. It has been deprecated and will be removed in the version ${version}.`; let fields: Record | undefined; - if (params.fields && (isUnsafeQueryParamsAllowed || !hasSupportedRoutes)) { + if (params.fields && isUnsafeQueryParamsAllowed) { try { apiDeprecationLogger.parameter(route, 'fields', '8.0.0', response, messageGenerator); fields = JSON.parse(params.fields) as Record; @@ -120,7 +108,7 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ } let query: Record = {}; - if (params.query && (isUnsafeQueryParamsAllowed || !hasSupportedRoutes)) { + if (params.query && isUnsafeQueryParamsAllowed) { apiDeprecationLogger.parameter(route, 'query', '8.0.0', response, messageGenerator); try { query = ejson.parse(params.query); diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 172a55024cce..b7187ec8cd81 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -409,8 +409,6 @@ API.v1.addRoute( { authRequired: true, validateParams: isUsersInfoParamsGetProps }, { async get() { - const { fields } = await this.parseJsonQuery(); - const searchTerms: [string, 'id' | 'username' | 'importId'] | false = ('userId' in this.queryParams && !!this.queryParams.userId && [this.queryParams.userId, 'id']) || ('username' in this.queryParams && !!this.queryParams.username && [this.queryParams.username, 'username']) || @@ -426,7 +424,7 @@ API.v1.addRoute( return API.v1.failure('User not found.'); } const myself = user._id === this.userId; - if (fields.userRooms === 1 && (myself || (await hasPermissionAsync(this.userId, 'view-other-user-channels')))) { + if (this.queryParams.includeUserRooms === 'true' && (myself || (await hasPermissionAsync(this.userId, 'view-other-user-channels')))) { return API.v1.success({ user: { ...user, diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index 0a1fb1a08286..712e1917441d 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -1290,23 +1290,6 @@ describe('[Rooms]', () => { }) .end(done); }); - it('should return name and _id of public channel when it has the "fields" query parameter limiting by name', (done) => { - void request - .get(api('rooms.info')) - .set(credentials) - .query({ - roomId: testChannel._id, - fields: JSON.stringify({ name: 1 }), - }) - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('room').and.to.be.an('object'); - expect(res.body.room).to.have.property('name').and.to.be.equal(testChannelName); - expect(res.body.room).to.have.all.keys(['_id', 'name']); - }) - .end(done); - }); it('should not return parent & team for room thats not on a team nor is a discussion', async () => { await request diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index 2752e1168073..87a27ecedfb4 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -722,7 +722,7 @@ describe('[Users]', () => { .set(credentials) .query({ userId: targetUser._id, - fields: JSON.stringify({ userRooms: 1 }), + includeUserRooms: true, }) .expect('Content-Type', 'application/json') .expect(200) @@ -750,6 +750,7 @@ describe('[Users]', () => { }) .end(done); }); + it('should return the rooms when the user request your own rooms but he does NOT have the necessary permission', (done) => { void updatePermission('view-other-user-channels', []).then(() => { void request @@ -757,7 +758,7 @@ describe('[Users]', () => { .set(credentials) .query({ userId: credentials['X-User-Id'], - fields: JSON.stringify({ userRooms: 1 }), + includeUserRooms: true, }) .expect('Content-Type', 'application/json') .expect(200) @@ -1100,37 +1101,6 @@ describe('[Users]', () => { .end(done); }); - it('should query all users in the system by custom fields', (done) => { - const query = { - fields: JSON.stringify({ - username: 1, - _id: 1, - customFields: 1, - }), - query: JSON.stringify({ - 'customFields.customFieldText': 'success', - }), - }; - - void request - .get(api('users.list')) - .query(query) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('users'); - const queriedUser = (res.body.users as IUser[]).find((u) => u._id === user._id); - assert.isDefined(queriedUser); - expect(queriedUser).to.have.property('customFields'); - expect(queriedUser.customFields).to.have.property('customFieldText', 'success'); - }) - .end(done); - }); - it('should sort for user statuses and check if deactivated user is correctly sorted', (done) => { const query = { fields: JSON.stringify({ @@ -1204,7 +1174,7 @@ describe('[Users]', () => { await request.get(api('users.list')).set(user2Credentials).expect('Content-Type', 'application/json').expect(403); }); - it('should exclude inviteToken in the user item for privileged users even when fields={inviteToken:1} is specified', async () => { + it('should exclude inviteToken in the user item for privileged users', async () => { await request .post(api('useInviteToken')) .set(user2Credentials) @@ -1236,7 +1206,7 @@ describe('[Users]', () => { }); }); - it('should exclude inviteToken in the user item for normal users even when fields={inviteToken:1} is specified', async () => { + it('should exclude inviteToken in the user item for normal users', async () => { await updateSetting('API_Apply_permission_view-outside-room_on_users-list', false); await request .post(api('useInviteToken')) diff --git a/packages/rest-typings/src/v1/users/UsersInfoParamsGet.ts b/packages/rest-typings/src/v1/users/UsersInfoParamsGet.ts index 1f07bed18475..ad1199881517 100644 --- a/packages/rest-typings/src/v1/users/UsersInfoParamsGet.ts +++ b/packages/rest-typings/src/v1/users/UsersInfoParamsGet.ts @@ -6,6 +6,7 @@ const ajv = new Ajv({ export type UsersInfoParamsGet = ({ userId: string } | { username: string } | { importId: string }) & { fields?: string; + includeUserRooms?: string; }; const UsersInfoParamsGetSchema = { @@ -16,6 +17,9 @@ const UsersInfoParamsGetSchema = { userId: { type: 'string', }, + includeUserRooms: { + type: 'string', + }, fields: { type: 'string', nullable: true, @@ -30,6 +34,9 @@ const UsersInfoParamsGetSchema = { username: { type: 'string', }, + includeUserRooms: { + type: 'string', + }, fields: { type: 'string', nullable: true, @@ -44,6 +51,9 @@ const UsersInfoParamsGetSchema = { importId: { type: 'string', }, + includeUserRooms: { + type: 'string', + }, fields: { type: 'string', nullable: true, From 82d8d523bc7cbfd4a648d09b948fec8f24fb3be5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Sun, 20 Oct 2024 00:12:32 -0300 Subject: [PATCH 08/12] regression: fix changeset typo --- .changeset/giant-spiders-train.md | 2 +- .changeset/tidy-suns-move.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/giant-spiders-train.md b/.changeset/giant-spiders-train.md index c05bb03779ba..59a68e78fd0a 100644 --- a/.changeset/giant-spiders-train.md +++ b/.changeset/giant-spiders-train.md @@ -1,5 +1,5 @@ --- - +'@rocket.chat/ui-kit': minor --- Adds `accessory` properties to `CalloutBlock` diff --git a/.changeset/tidy-suns-move.md b/.changeset/tidy-suns-move.md index 4d125d059b68..ab2bec1b72c1 100644 --- a/.changeset/tidy-suns-move.md +++ b/.changeset/tidy-suns-move.md @@ -1,6 +1,6 @@ --- -"@rocket.chat/meteor": feat -"@rocket.chat/i18n": feat +'@rocket.chat/meteor': minor +'@rocket.chat/i18n': minor --- Introduces new visual components into marketplace pages to inform an add-on necessity into the workspace. From 7957a443d8c32d647764ccbac226b4ee30f366aa Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Sun, 20 Oct 2024 01:40:52 -0300 Subject: [PATCH 09/12] ci: onlyUpdatePeerDependentsWhenOutOfRange to avoid major bumps --- .changeset/config.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index b93fac2c92a3..ab919c28b248 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,12 +2,13 @@ "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", "changelog": ["@rocket.chat/release-changelog", { "repo": "RocketChat/Rocket.Chat" }], "commit": false, - "fixed": [ - ["@rocket.chat/meteor", "@rocket.chat/core-typings", "@rocket.chat/rest-typings"] - ], + "fixed": [["@rocket.chat/meteor", "@rocket.chat/core-typings", "@rocket.chat/rest-typings"]], "linked": [], "access": "public", "baseBranch": "develop", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": [], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } } From ddc64d85df93f447da79a7511d32f468538dd500 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Sun, 20 Oct 2024 01:27:05 -0300 Subject: [PATCH 10/12] chore: remove peer dep to avoid major releases on changeset --- packages/apps-engine/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/apps-engine/package.json b/packages/apps-engine/package.json index c46c255ae56c..8657a7e47529 100644 --- a/packages/apps-engine/package.json +++ b/packages/apps-engine/package.json @@ -104,9 +104,6 @@ "stack-trace": "0.0.10", "uuid": "~8.3.2" }, - "peerDependencies": { - "@rocket.chat/ui-kit": "workspace:^" - }, "nyc": { "include": [ "src/*.ts", From 5fddfcc99ea6d17eb63cd33772dc7388a1d7fba8 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 21 Oct 2024 10:27:29 -0300 Subject: [PATCH 11/12] refactor: Migrate more modules to TypeScript (#33636) --- .../startup/{responses.js => responses.ts} | 23 +- .../app/e2e/client/{events.js => events.ts} | 2 +- .../app/e2e/client/{helper.js => helper.ts} | 64 ++- ...hat.e2e.room.js => rocketchat.e2e.room.ts} | 110 ++-- apps/meteor/app/e2e/client/rocketchat.e2e.ts | 2 +- .../lib/{emojiCustom.js => emojiCustom.ts} | 80 +-- .../emoji-custom/client/lib/function-isSet.js | 9 - .../client/{emojiParser.js => emojiParser.ts} | 16 +- apps/meteor/app/emoji/lib/rocketchat.ts | 32 +- .../server/handler/{index.js => index.ts} | 18 +- .../localHandlers/{index.js => index.ts} | 0 .../peerHandlers/{index.js => index.ts} | 0 .../irc/server/servers/{index.js => index.ts} | 0 .../client/{OAuthProxy.js => OAuthProxy.ts} | 4 +- .../client/collections/LivechatInquiry.js | 3 - .../client/collections/LivechatInquiry.ts | 4 + .../client/lib/stream/queueManager.ts | 6 +- ...client.js => slackbridge_import.client.ts} | 2 +- .../client/lib/normalizeThreadTitle.ts | 4 +- .../ui-master/server/{index.js => index.ts} | 29 +- apps/meteor/app/ui-master/server/inject.ts | 4 +- .../client/{WebRTCClass.js => WebRTCClass.ts} | 473 +++++++++++------- apps/meteor/app/webrtc/client/adapter.js | 6 - apps/meteor/app/webrtc/client/adapter.ts | 7 + .../client/{screenShare.js => screenShare.ts} | 21 +- .../meteor/client/components/MarkdownText.tsx | 2 +- apps/meteor/client/definitions/global.d.ts | 75 +++ .../client/lib/utils/renderMessageEmoji.ts | 2 +- .../client/providers/OmnichannelProvider.tsx | 13 +- apps/meteor/client/views/meet/CallPage.tsx | 44 +- apps/meteor/definition/externals/global.d.ts | 24 - .../meteor/meteorhacks-inject-initial.d.ts | 1 + .../apps/storage/{index.js => index.ts} | 0 apps/meteor/package.json | 1 + packages/base64/src/base64.ts | 18 +- packages/ddp-client/src/types/streams.ts | 77 ++- yarn.lock | 18 + 37 files changed, 742 insertions(+), 452 deletions(-) rename apps/meteor/app/canned-responses/client/startup/{responses.js => responses.ts} (72%) rename apps/meteor/app/e2e/client/{events.js => events.ts} (83%) rename apps/meteor/app/e2e/client/{helper.js => helper.ts} (64%) rename apps/meteor/app/e2e/client/{rocketchat.e2e.room.js => rocketchat.e2e.room.ts} (88%) rename apps/meteor/app/emoji-custom/client/lib/{emojiCustom.js => emojiCustom.ts} (62%) delete mode 100644 apps/meteor/app/emoji-custom/client/lib/function-isSet.js rename apps/meteor/app/emoji/client/{emojiParser.js => emojiParser.ts} (74%) rename apps/meteor/app/federation/server/handler/{index.js => index.ts} (80%) rename apps/meteor/app/irc/server/irc-bridge/localHandlers/{index.js => index.ts} (100%) rename apps/meteor/app/irc/server/irc-bridge/peerHandlers/{index.js => index.ts} (100%) rename apps/meteor/app/irc/server/servers/{index.js => index.ts} (100%) rename apps/meteor/app/lib/client/{OAuthProxy.js => OAuthProxy.ts} (86%) delete mode 100644 apps/meteor/app/livechat/client/collections/LivechatInquiry.js create mode 100644 apps/meteor/app/livechat/client/collections/LivechatInquiry.ts rename apps/meteor/app/slackbridge/client/{slackbridge_import.client.js => slackbridge_import.client.ts} (85%) rename apps/meteor/app/ui-master/server/{index.js => index.ts} (81%) rename apps/meteor/app/webrtc/client/{WebRTCClass.js => WebRTCClass.ts} (66%) delete mode 100644 apps/meteor/app/webrtc/client/adapter.js create mode 100644 apps/meteor/app/webrtc/client/adapter.ts rename apps/meteor/app/webrtc/client/{screenShare.js => screenShare.ts} (77%) rename apps/meteor/ee/server/apps/storage/{index.js => index.ts} (100%) diff --git a/apps/meteor/app/canned-responses/client/startup/responses.js b/apps/meteor/app/canned-responses/client/startup/responses.ts similarity index 72% rename from apps/meteor/app/canned-responses/client/startup/responses.js rename to apps/meteor/app/canned-responses/client/startup/responses.ts index 595945283261..6d761adb890d 100644 --- a/apps/meteor/app/canned-responses/client/startup/responses.js +++ b/apps/meteor/app/canned-responses/client/startup/responses.ts @@ -6,13 +6,6 @@ import { settings } from '../../../settings/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; import { CannedResponse } from '../collections/CannedResponse'; -const events = { - changed: ({ type, ...response }) => { - CannedResponse.upsert({ _id: response._id }, response); - }, - removed: (response) => CannedResponse.remove({ _id: response._id }), -}; - Meteor.startup(() => { Tracker.autorun(async (c) => { if (!Meteor.userId()) { @@ -27,12 +20,24 @@ Meteor.startup(() => { Tracker.afterFlush(() => { try { // TODO: check options - sdk.stream('canned-responses', ['canned-responses'], (response, options) => { + sdk.stream('canned-responses', ['canned-responses'], (...[response, options]) => { const { agentsId } = options || {}; if (Array.isArray(agentsId) && !agentsId.includes(Meteor.userId())) { return; } - events[response.type](response); + + switch (response.type) { + case 'changed': { + const { type, ...fields } = response; + CannedResponse.upsert({ _id: response._id }, fields); + break; + } + + case 'removed': { + CannedResponse.remove({ _id: response._id }); + break; + } + } }); } catch (error) { console.log(error); diff --git a/apps/meteor/app/e2e/client/events.js b/apps/meteor/app/e2e/client/events.ts similarity index 83% rename from apps/meteor/app/e2e/client/events.js rename to apps/meteor/app/e2e/client/events.ts index c59b20594b85..9ccef3d7b28d 100644 --- a/apps/meteor/app/e2e/client/events.js +++ b/apps/meteor/app/e2e/client/events.ts @@ -3,5 +3,5 @@ import { Accounts } from 'meteor/accounts-base'; import { e2e } from './rocketchat.e2e'; Accounts.onLogout(() => { - e2e.stopClient(); + void e2e.stopClient(); }); diff --git a/apps/meteor/app/e2e/client/helper.js b/apps/meteor/app/e2e/client/helper.ts similarity index 64% rename from apps/meteor/app/e2e/client/helper.js rename to apps/meteor/app/e2e/client/helper.ts index 25d9e9407801..66ca3bf1cc2e 100644 --- a/apps/meteor/app/e2e/client/helper.js +++ b/apps/meteor/app/e2e/client/helper.ts @@ -1,24 +1,20 @@ import { Random } from '@rocket.chat/random'; import ByteBuffer from 'bytebuffer'; -// eslint-disable-next-line no-proto -const StaticArrayBufferProto = new ArrayBuffer().__proto__; - -export function toString(thing) { +export function toString(thing: any) { if (typeof thing === 'string') { return thing; } - // eslint-disable-next-line new-cap - return new ByteBuffer.wrap(thing).toString('binary'); + + return ByteBuffer.wrap(thing).toString('binary'); } -export function toArrayBuffer(thing) { +export function toArrayBuffer(thing: any) { if (thing === undefined) { return undefined; } - if (thing === Object(thing)) { - // eslint-disable-next-line no-proto - if (thing.__proto__ === StaticArrayBufferProto) { + if (typeof thing === 'object') { + if (Object.getPrototypeOf(thing) === ArrayBuffer.prototype) { return thing; } } @@ -26,11 +22,11 @@ export function toArrayBuffer(thing) { if (typeof thing !== 'string') { throw new Error(`Tried to convert a non-string of type ${typeof thing} to an array buffer`); } - // eslint-disable-next-line new-cap - return new ByteBuffer.wrap(thing, 'binary').toArrayBuffer(); + + return ByteBuffer.wrap(thing, 'binary').toArrayBuffer(); } -export function joinVectorAndEcryptedData(vector, encryptedData) { +export function joinVectorAndEcryptedData(vector: any, encryptedData: any) { const cipherText = new Uint8Array(encryptedData); const output = new Uint8Array(vector.length + cipherText.length); output.set(vector, 0); @@ -38,30 +34,30 @@ export function joinVectorAndEcryptedData(vector, encryptedData) { return output; } -export function splitVectorAndEcryptedData(cipherText) { +export function splitVectorAndEcryptedData(cipherText: any) { const vector = cipherText.slice(0, 16); const encryptedData = cipherText.slice(16); return [vector, encryptedData]; } -export async function encryptRSA(key, data) { +export async function encryptRSA(key: any, data: any) { return crypto.subtle.encrypt({ name: 'RSA-OAEP' }, key, data); } -export async function encryptAES(vector, key, data) { +export async function encryptAES(vector: any, key: any, data: any) { return crypto.subtle.encrypt({ name: 'AES-CBC', iv: vector }, key, data); } -export async function encryptAESCTR(vector, key, data) { +export async function encryptAESCTR(vector: any, key: any, data: any) { return crypto.subtle.encrypt({ name: 'AES-CTR', counter: vector, length: 64 }, key, data); } -export async function decryptRSA(key, data) { +export async function decryptRSA(key: any, data: any) { return crypto.subtle.decrypt({ name: 'RSA-OAEP' }, key, data); } -export async function decryptAES(vector, key, data) { +export async function decryptAES(vector: any, key: any, data: any) { return crypto.subtle.decrypt({ name: 'AES-CBC', iv: vector }, key, data); } @@ -86,54 +82,54 @@ export async function generateRSAKey() { ); } -export async function exportJWKKey(key) { +export async function exportJWKKey(key: any) { return crypto.subtle.exportKey('jwk', key); } -export async function importRSAKey(keyData, keyUsages = ['encrypt', 'decrypt']) { +export async function importRSAKey(keyData: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { return crypto.subtle.importKey( - 'jwk', + 'jwk' as any, keyData, { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' }, - }, + } as any, true, keyUsages, ); } -export async function importAESKey(keyData, keyUsages = ['encrypt', 'decrypt']) { +export async function importAESKey(keyData: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { return crypto.subtle.importKey('jwk', keyData, { name: 'AES-CBC' }, true, keyUsages); } -export async function importRawKey(keyData, keyUsages = ['deriveKey']) { +export async function importRawKey(keyData: any, keyUsages: ReadonlyArray = ['deriveKey']) { return crypto.subtle.importKey('raw', keyData, { name: 'PBKDF2' }, false, keyUsages); } -export async function deriveKey(salt, baseKey, keyUsages = ['encrypt', 'decrypt']) { +export async function deriveKey(salt: any, baseKey: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { const iterations = 1000; const hash = 'SHA-256'; return crypto.subtle.deriveKey({ name: 'PBKDF2', salt, iterations, hash }, baseKey, { name: 'AES-CBC', length: 256 }, true, keyUsages); } -export async function readFileAsArrayBuffer(file) { - return new Promise((resolve, reject) => { +export async function readFileAsArrayBuffer(file: any) { + return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = function (evt) { - resolve(evt.target.result); + reader.onload = (evt) => { + resolve(evt.target?.result); }; - reader.onerror = function (evt) { + reader.onerror = (evt) => { reject(evt); }; reader.readAsArrayBuffer(file); }); } -export async function generateMnemonicPhrase(n, sep = ' ') { +export async function generateMnemonicPhrase(n: any, sep = ' ') { const { default: wordList } = await import('./wordList'); const result = new Array(n); let len = wordList.length; @@ -147,14 +143,14 @@ export async function generateMnemonicPhrase(n, sep = ' ') { return result.join(sep); } -export async function createSha256HashFromText(data) { +export async function createSha256HashFromText(data: any) { const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data)); return Array.from(new Uint8Array(hash)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } -export async function sha256HashFromArrayBuffer(arrayBuffer) { +export async function sha256HashFromArrayBuffer(arrayBuffer: any) { const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', arrayBuffer))); return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); } diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts similarity index 88% rename from apps/meteor/app/e2e/client/rocketchat.e2e.room.js rename to apps/meteor/app/e2e/client/rocketchat.e2e.room.ts index 4c9de837dce0..ff1841a7ef86 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts @@ -34,7 +34,7 @@ import { e2e } from './rocketchat.e2e'; const KEY_ID = Symbol('keyID'); const PAUSED = Symbol('PAUSED'); -const permitedMutations = { +const permitedMutations: any = { [E2ERoomState.NOT_STARTED]: [E2ERoomState.ESTABLISHING, E2ERoomState.DISABLED, E2ERoomState.KEYS_RECEIVED], [E2ERoomState.READY]: [E2ERoomState.DISABLED, E2ERoomState.CREATING_KEYS, E2ERoomState.WAITING_KEYS], [E2ERoomState.ERROR]: [E2ERoomState.KEYS_RECEIVED, E2ERoomState.NOT_STARTED], @@ -49,7 +49,7 @@ const permitedMutations = { ], }; -const filterMutation = (currentState, nextState) => { +const filterMutation = (currentState: any, nextState: any): any => { if (currentState === nextState) { return nextState === E2ERoomState.ERROR; } @@ -66,11 +66,29 @@ const filterMutation = (currentState, nextState) => { }; export class E2ERoom extends Emitter { - state = undefined; + state: any = undefined; - [PAUSED] = undefined; + [PAUSED]: boolean | undefined = undefined; - constructor(userId, room) { + [KEY_ID]: any; + + userId: any; + + roomId: any; + + typeOfRoom: any; + + roomKeyId: any; + + groupSessionKey: any; + + oldKeys: any; + + sessionKeyExportedString: string | undefined; + + sessionKeyExported: any; + + constructor(userId: any, room: any) { super(); this.userId = userId; @@ -93,11 +111,11 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.NOT_STARTED); } - log(...msg) { + log(...msg: unknown[]) { log(`E2E ROOM { state: ${this.state}, rid: ${this.roomId} }`, ...msg); } - error(...msg) { + error(...msg: unknown[]) { logError(`E2E ROOM { state: ${this.state}, rid: ${this.roomId} }`, ...msg); } @@ -109,7 +127,7 @@ export class E2ERoom extends Emitter { return this.state; } - setState(requestedState) { + setState(requestedState: any) { const currentState = this.state; const nextState = filterMutation(currentState, requestedState); @@ -120,7 +138,7 @@ export class E2ERoom extends Emitter { this.state = nextState; this.log(currentState, '->', nextState); - this.emit('STATE_CHANGED', currentState, nextState, this); + this.emit('STATE_CHANGED', currentState); this.emit(nextState, this); } @@ -160,7 +178,7 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.KEYS_RECEIVED); } - async shouldConvertSentMessages(message) { + async shouldConvertSentMessages(message: any) { if (!this.isReady() || this[PAUSED]) { return false; } @@ -197,7 +215,7 @@ export class E2ERoom extends Emitter { async decryptSubscription() { const subscription = Subscriptions.findOne({ rid: this.roomId }); - if (subscription.lastMessage?.t !== 'e2e') { + if (subscription?.lastMessage?.t !== 'e2e') { this.log('decryptSubscriptions nothing to do'); return; } @@ -245,7 +263,7 @@ export class E2ERoom extends Emitter { this.log('decryptOldRoomKeys Done'); } - async exportOldRoomKeys(oldKeys) { + async exportOldRoomKeys(oldKeys: any) { this.log('exportOldRoomKeys starting'); if (!oldKeys || oldKeys.length === 0) { this.log('exportOldRoomKeys nothing to do'); @@ -294,7 +312,7 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.ESTABLISHING); try { - const groupKey = Subscriptions.findOne({ rid: this.roomId }).E2EKey; + const groupKey = Subscriptions.findOne({ rid: this.roomId })?.E2EKey; if (groupKey) { await this.importGroupKey(groupKey); this.setState(E2ERoomState.READY); @@ -307,7 +325,7 @@ export class E2ERoom extends Emitter { } try { - const room = ChatRoom.findOne({ _id: this.roomId }); + const room = ChatRoom.findOne({ _id: this.roomId })!; // Only room creator can set keys for room if (!room.e2eKeyId && this.userShouldCreateKeys(room)) { this.setState(E2ERoomState.CREATING_KEYS); @@ -325,7 +343,7 @@ export class E2ERoom extends Emitter { } } - userShouldCreateKeys(room) { + userShouldCreateKeys(room: any) { // On DMs, we'll allow any user to set the keys if (room.t === 'd') { return true; @@ -334,15 +352,15 @@ export class E2ERoom extends Emitter { return room.u._id === this.userId; } - isSupportedRoomType(type) { + isSupportedRoomType(type: any) { return roomCoordinator.getRoomDirectives(type).allowRoomSettingChange({}, RoomSettingsEnum.E2E); } - async decryptSessionKey(key) { + async decryptSessionKey(key: any) { return importAESKey(JSON.parse(await this.exportSessionKey(key))); } - async exportSessionKey(key) { + async exportSessionKey(key: any) { key = key.slice(12); key = Base64.decode(key); @@ -350,7 +368,7 @@ export class E2ERoom extends Emitter { return toString(decryptedKey); } - async importGroupKey(groupKey) { + async importGroupKey(groupKey: any) { this.log('Importing room key ->', this.roomId); // Get existing group key // const keyID = groupKey.slice(0, 12); @@ -374,7 +392,7 @@ export class E2ERoom extends Emitter { // Import session key for use. try { - const key = await importAESKey(JSON.parse(this.sessionKeyExportedString)); + const key = await importAESKey(JSON.parse(this.sessionKeyExportedString!)); // Key has been obtained. E2E is now in session. this.groupSessionKey = key; } catch (error) { @@ -402,8 +420,8 @@ export class E2ERoom extends Emitter { await sdk.rest.post('/v1/e2e.updateGroupKey', { rid: this.roomId, uid: this.userId, - key: await this.encryptGroupKeyForParticipant(e2e.publicKey), - }); + key: await this.encryptGroupKeyForParticipant(e2e.publicKey!), + } as any); await this.encryptKeyForOtherParticipants(); } catch (error) { this.error('Error exporting group key: ', error); @@ -434,7 +452,7 @@ export class E2ERoom extends Emitter { } } - onRoomKeyReset(keyID) { + onRoomKeyReset(keyID: any) { this.log(`Room keyID was reset. New keyID: ${keyID} Previous keyID: ${this.keyID}`); this.setState(E2ERoomState.WAITING_KEYS); this.keyID = keyID; @@ -455,10 +473,10 @@ export class E2ERoom extends Emitter { return; } - const usersSuggestedGroupKeys = { [this.roomId]: [] }; + const usersSuggestedGroupKeys = { [this.roomId]: [] as any[] }; for await (const user of users) { - const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e.public_key); - const oldKeys = await this.encryptOldKeysForParticipant(user.e2e.public_key, decryptedOldGroupKeys); + const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e!.public_key!); + const oldKeys = await this.encryptOldKeysForParticipant(user.e2e?.public_key, decryptedOldGroupKeys); usersSuggestedGroupKeys[this.roomId].push({ _id: user._id, key: encryptedGroupKey, ...(oldKeys && { oldKeys }) }); } @@ -469,7 +487,7 @@ export class E2ERoom extends Emitter { } } - async encryptOldKeysForParticipant(public_key, oldRoomKeys) { + async encryptOldKeysForParticipant(publicKey: any, oldRoomKeys: any) { if (!oldRoomKeys || oldRoomKeys.length === 0) { return; } @@ -477,7 +495,7 @@ export class E2ERoom extends Emitter { let userKey; try { - userKey = await importRSAKey(JSON.parse(public_key), ['encrypt']); + userKey = await importRSAKey(JSON.parse(publicKey), ['encrypt']); } catch (error) { return this.error('Error importing user key: ', error); } @@ -499,10 +517,10 @@ export class E2ERoom extends Emitter { } } - async encryptGroupKeyForParticipant(public_key) { + async encryptGroupKeyForParticipant(publicKey: string) { let userKey; try { - userKey = await importRSAKey(JSON.parse(public_key), ['encrypt']); + userKey = await importRSAKey(JSON.parse(publicKey), ['encrypt']); } catch (error) { return this.error('Error importing user key: ', error); } @@ -519,7 +537,7 @@ export class E2ERoom extends Emitter { } // Encrypts files before upload. I/O is in arraybuffers. - async encryptFile(file) { + async encryptFile(file: any) { // if (!this.isSupportedRoomType(this.typeOfRoom)) { // return; // } @@ -554,7 +572,7 @@ export class E2ERoom extends Emitter { } // Decrypt uploaded encrypted files. I/O is in arraybuffers. - async decryptFile(file, key, iv) { + async decryptFile(file: any, key: any, iv: any) { const ivArray = Base64.decode(iv); const cryptoKey = await window.crypto.subtle.importKey('jwk', key, { name: 'AES-CTR' }, true, ['encrypt', 'decrypt']); @@ -562,7 +580,7 @@ export class E2ERoom extends Emitter { } // Encrypts messages - async encryptText(data) { + async encryptText(data: any) { const vector = crypto.getRandomValues(new Uint8Array(16)); try { @@ -575,7 +593,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of content - async encryptMessageContent(contentToBeEncrypted) { + async encryptMessageContent(contentToBeEncrypted: any) { const data = new TextEncoder().encode(EJSON.stringify(contentToBeEncrypted)); return { @@ -585,7 +603,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of content - async encryptMessage(message) { + async encryptMessage(message: any) { const { msg, attachments, ...rest } = message; const content = await this.encryptMessageContent({ msg, attachments }); @@ -599,7 +617,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of messages - encrypt(message) { + encrypt(message: any) { if (!this.isSupportedRoomType(this.typeOfRoom)) { return; } @@ -610,7 +628,7 @@ export class E2ERoom extends Emitter { const ts = new Date(); - const data = new TextEncoder('UTF-8').encode( + const data = new TextEncoder().encode( EJSON.stringify({ _id: message._id, text: message.msg, @@ -622,7 +640,7 @@ export class E2ERoom extends Emitter { return this.encryptText(data); } - async decryptContent(data) { + async decryptContent(data: any) { if (data.content && data.content.algorithm === 'rc.v1.aes-sha2') { const content = await this.decrypt(data.content.ciphertext); Object.assign(data, content); @@ -632,7 +650,7 @@ export class E2ERoom extends Emitter { } // Decrypt messages - async decryptMessage(message) { + async decryptMessage(message: any) { if (message.t !== 'e2e' || message.e2e === 'done') { return message; } @@ -653,12 +671,12 @@ export class E2ERoom extends Emitter { }; } - async doDecrypt(vector, key, cipherText) { + async doDecrypt(vector: any, key: any, cipherText: any) { const result = await decryptAES(vector, key, cipherText); return EJSON.parse(new TextDecoder('UTF-8').decode(new Uint8Array(result))); } - async decrypt(message) { + async decrypt(message: any) { const keyID = message.slice(0, 12); message = message.slice(12); @@ -666,7 +684,7 @@ export class E2ERoom extends Emitter { let oldKey = ''; if (keyID !== this.keyID) { - const oldRoomKey = this.oldKeys?.find((key) => key.e2eKeyId === keyID); + const oldRoomKey = this.oldKeys?.find((key: any) => key.e2eKeyId === keyID); // Messages already contain a keyID stored with them // That means that if we cannot find a keyID for the key the message has preppended to // The message is indecipherable. @@ -691,21 +709,21 @@ export class E2ERoom extends Emitter { } } - provideKeyToUser(keyId) { + provideKeyToUser(keyId: any) { if (this.keyID !== keyId) { return; } - this.encryptKeyForOtherParticipants(); + void this.encryptKeyForOtherParticipants(); this.setState(E2ERoomState.READY); } - onStateChange(cb) { + onStateChange(cb: any) { this.on('STATE_CHANGED', cb); return () => this.off('STATE_CHANGED', cb); } - async encryptGroupKeyForParticipantsWaitingForTheKeys(users) { + async encryptGroupKeyForParticipantsWaitingForTheKeys(users: any[]) { if (!this.isReady()) { return; } diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 3b2fd01621e4..824afc3aa2d5 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -44,7 +44,7 @@ import { import { log, logError } from './logger'; import { E2ERoom } from './rocketchat.e2e.room'; -import './events.js'; +import './events'; let failedToDecodeKey = false; diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts similarity index 62% rename from apps/meteor/app/emoji-custom/client/lib/emojiCustom.js rename to apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts index 64f1df9bd932..ee186ebd4e15 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts @@ -1,3 +1,4 @@ +import type { IEmoji } from '@rocket.chat/core-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; @@ -6,52 +7,61 @@ import { emoji, updateRecent } from '../../../emoji/client'; import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { getURL } from '../../../utils/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; -import { isSetNotNull } from './function-isSet'; -export const getEmojiUrlFromName = function (name, extension) { +const isSetNotNull = (fn: () => unknown) => { + let value; + try { + value = fn(); + } catch (e) { + value = null; + } + return value !== null && value !== undefined; +}; + +const getEmojiUrlFromName = (name: string, extension: string) => { if (name == null) { return; } - const key = `emoji_random_${name}`; + const key = `emoji_random_${name}` as const; - const random = isSetNotNull(() => Session.keys[key]) ? Session.keys[key] : 0; + const random = (Session as unknown as { keys: Record }).keys[key] ?? 0; return getURL(`/emoji-custom/${encodeURIComponent(name)}.${extension}?_dc=${random}`); }; -export const deleteEmojiCustom = function (emojiData) { +export const deleteEmojiCustom = (emojiData: IEmoji) => { delete emoji.list[`:${emojiData.name}:`]; const arrayIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(emojiData.name); if (arrayIndex !== -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.splice(arrayIndex, 1); } - const arrayIndexList = emoji.packages.emojiCustom.list.indexOf(`:${emojiData.name}:`); + const arrayIndexList = emoji.packages.emojiCustom.list?.indexOf(`:${emojiData.name}:`) ?? -1; if (arrayIndexList !== -1) { - emoji.packages.emojiCustom.list.splice(arrayIndexList, 1); + emoji.packages.emojiCustom.list?.splice(arrayIndexList, 1); } - if (isSetNotNull(() => emojiData.aliases)) { + if (emojiData.aliases) { for (const alias of emojiData.aliases) { delete emoji.list[`:${alias}:`]; - const aliasIndex = emoji.packages.emojiCustom.list.indexOf(`:${alias}:`); + const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1; if (aliasIndex !== -1) { - emoji.packages.emojiCustom.list.splice(aliasIndex, 1); + emoji.packages.emojiCustom.list?.splice(aliasIndex, 1); } } } - updateRecent('rocket'); + updateRecent(['rocket']); }; -export const updateEmojiCustom = function (emojiData) { +export const updateEmojiCustom = (emojiData: IEmoji) => { const previousExists = isSetNotNull(() => emojiData.previousName); const currentAliases = isSetNotNull(() => emojiData.aliases); if (previousExists && isSetNotNull(() => emoji.list[`:${emojiData.previousName}:`].aliases)) { - for (const alias of emoji.list[`:${emojiData.previousName}:`].aliases) { + for (const alias of emoji.list[`:${emojiData.previousName}:`].aliases ?? []) { delete emoji.list[`:${alias}:`]; - const aliasIndex = emoji.packages.emojiCustom.list.indexOf(`:${alias}:`); + const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1; if (aliasIndex !== -1) { - emoji.packages.emojiCustom.list.splice(aliasIndex, 1); + emoji.packages.emojiCustom.list?.splice(aliasIndex, 1); } } } @@ -61,9 +71,9 @@ export const updateEmojiCustom = function (emojiData) { if (arrayIndex !== -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.splice(arrayIndex, 1); } - const arrayIndexList = emoji.packages.emojiCustom.list.indexOf(`:${emojiData.previousName}:`); + const arrayIndexList = emoji.packages.emojiCustom.list?.indexOf(`:${emojiData.previousName}:`) ?? -1; if (arrayIndexList !== -1) { - emoji.packages.emojiCustom.list.splice(arrayIndexList, 1); + emoji.packages.emojiCustom.list?.splice(arrayIndexList, 1); } delete emoji.list[`:${emojiData.previousName}:`]; } @@ -71,23 +81,24 @@ export const updateEmojiCustom = function (emojiData) { const categoryIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(`${emojiData.name}`); if (categoryIndex === -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.push(`${emojiData.name}`); - emoji.packages.emojiCustom.list.push(`:${emojiData.name}:`); + emoji.packages.emojiCustom.list?.push(`:${emojiData.name}:`); } emoji.list[`:${emojiData.name}:`] = Object.assign({ emojiPackage: 'emojiCustom' }, emoji.list[`:${emojiData.name}:`], emojiData); if (currentAliases) { for (const alias of emojiData.aliases) { - emoji.packages.emojiCustom.list.push(`:${alias}:`); - emoji.list[`:${alias}:`] = {}; - emoji.list[`:${alias}:`].emojiPackage = 'emojiCustom'; - emoji.list[`:${alias}:`].aliasOf = emojiData.name; + emoji.packages.emojiCustom.list?.push(`:${alias}:`); + emoji.list[`:${alias}:`] = { + emojiPackage: 'emojiCustom', + aliasOf: emojiData.name, + }; } } - updateRecent('rocket'); + updateRecent(['rocket']); }; -const customRender = (html) => { - const emojisMatchGroup = emoji.packages.emojiCustom.list.map(escapeRegExp).join('|'); +const customRender = (html: string) => { + const emojisMatchGroup = emoji.packages.emojiCustom.list?.map(escapeRegExp).join('|'); if (emojisMatchGroup !== emoji.packages.emojiCustom._regexpSignature) { emoji.packages.emojiCustom._regexpSignature = emojisMatchGroup; emoji.packages.emojiCustom._regexp = new RegExp( @@ -96,22 +107,22 @@ const customRender = (html) => { ); } - html = html.replace(emoji.packages.emojiCustom._regexp, (shortname) => { - if (typeof shortname === 'undefined' || shortname === '' || emoji.packages.emojiCustom.list.indexOf(shortname) === -1) { + html = html.replace(emoji.packages.emojiCustom._regexp!, (shortname) => { + if (typeof shortname === 'undefined' || shortname === '' || (emoji.packages.emojiCustom.list?.indexOf(shortname) ?? -1) === -1) { return shortname; } let emojiAlias = shortname.replace(/:/g, ''); let dataCheck = emoji.list[shortname]; - if (dataCheck.hasOwnProperty('aliasOf')) { + if (dataCheck.aliasOf) { emojiAlias = dataCheck.aliasOf; dataCheck = emoji.list[`:${emojiAlias}:`]; } return `${shortname}`; }); @@ -125,7 +136,7 @@ emoji.packages.emojiCustom = { list: [], _regexpSignature: null, _regexp: null, - + emojisByCategory: {}, render: customRender, renderPicker: customRender, }; @@ -135,16 +146,15 @@ Meteor.startup(() => try { const { emojis: { update: emojis }, - } = await sdk.rest.get('/v1/emoji-custom.list'); + } = await sdk.rest.get('/v1/emoji-custom.list', { query: '' }); emoji.packages.emojiCustom.emojisByCategory = { rocket: [] }; for (const currentEmoji of emojis) { emoji.packages.emojiCustom.emojisByCategory.rocket.push(currentEmoji.name); - emoji.packages.emojiCustom.list.push(`:${currentEmoji.name}:`); - emoji.list[`:${currentEmoji.name}:`] = currentEmoji; - emoji.list[`:${currentEmoji.name}:`].emojiPackage = 'emojiCustom'; + emoji.packages.emojiCustom.list?.push(`:${currentEmoji.name}:`); + emoji.list[`:${currentEmoji.name}:`] = { ...currentEmoji, emojiPackage: 'emojiCustom' } as any; for (const alias of currentEmoji.aliases) { - emoji.packages.emojiCustom.list.push(`:${alias}:`); + emoji.packages.emojiCustom.list?.push(`:${alias}:`); emoji.list[`:${alias}:`] = { emojiPackage: 'emojiCustom', aliasOf: currentEmoji.name, diff --git a/apps/meteor/app/emoji-custom/client/lib/function-isSet.js b/apps/meteor/app/emoji-custom/client/lib/function-isSet.js deleted file mode 100644 index 0ccf1abe02ab..000000000000 --- a/apps/meteor/app/emoji-custom/client/lib/function-isSet.js +++ /dev/null @@ -1,9 +0,0 @@ -export const isSetNotNull = function (fn) { - let value; - try { - value = fn(); - } catch (e) { - value = null; - } - return value !== null && value !== undefined; -}; diff --git a/apps/meteor/app/emoji/client/emojiParser.js b/apps/meteor/app/emoji/client/emojiParser.ts similarity index 74% rename from apps/meteor/app/emoji/client/emojiParser.js rename to apps/meteor/app/emoji/client/emojiParser.ts index 0b3b722aaebd..08ec99b06958 100644 --- a/apps/meteor/app/emoji/client/emojiParser.js +++ b/apps/meteor/app/emoji/client/emojiParser.ts @@ -3,10 +3,8 @@ import { emoji } from './lib'; /** * emojiParser is a function that will replace emojis - * @param {{ html: string }} message - The message object - * @return {{ html: string }} */ -export const emojiParser = ({ html }) => { +export const emojiParser = (html: string) => { html = html.trim(); // ' to apostrophe (') for emojis such as :') @@ -28,8 +26,12 @@ export const emojiParser = ({ html }) => { let hasText = false; if (!isIE11) { - const filter = (node) => { - if (node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('emojione') || node.classList.contains('emoji'))) { + const isElement = (node: Node): node is Element => node.nodeType === Node.ELEMENT_NODE; + + const isTextNode = (node: Node): node is Text => node.nodeType === Node.TEXT_NODE; + + const filter = (node: Node) => { + if (isElement(node) && (node.classList.contains('emojione') || node.classList.contains('emoji'))) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; @@ -38,7 +40,7 @@ export const emojiParser = ({ html }) => { const walker = document.createTreeWalker(checkEmojiOnly, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, filter); while (walker.nextNode()) { - if (walker.currentNode.nodeType === Node.TEXT_NODE && walker.currentNode.nodeValue.trim() !== '') { + if (isTextNode(walker.currentNode) && walker.currentNode.nodeValue.trim() !== '') { hasText = true; break; } @@ -60,5 +62,5 @@ export const emojiParser = ({ html }) => { // line breaks '
' back to '
' html = html.replace(/
/g, '
'); - return { html }; + return html; }; diff --git a/apps/meteor/app/emoji/lib/rocketchat.ts b/apps/meteor/app/emoji/lib/rocketchat.ts index 49d6ffbe41aa..f5d33cce3de0 100644 --- a/apps/meteor/app/emoji/lib/rocketchat.ts +++ b/apps/meteor/app/emoji/lib/rocketchat.ts @@ -9,6 +9,9 @@ export type EmojiPackage = { renderPicker: (emojiToRender: string) => string | undefined; ascii?: boolean; sprites?: unknown; + list?: string[]; + _regexpSignature?: string | null; + _regexp?: RegExp | null; }; export type EmojiPackages = { @@ -16,14 +19,25 @@ export type EmojiPackages = { [key: string]: EmojiPackage; }; list: { - [key: keyof NonNullable]: { - category: string; - emojiPackage: string; - shortnames: string[]; - uc_base: string; - uc_greedy: string; - uc_match: string; - uc_output: string; - }; + [key: keyof NonNullable]: + | { + category: string; + emojiPackage: string; + shortnames: string[]; + uc_base: string; + uc_greedy: string; + uc_match: string; + uc_output: string; + aliases?: string[]; + aliasOf?: undefined; + extension?: string; + } + | { + emojiPackage: string; + aliasOf: string; + extension?: undefined; + aliases?: undefined; + shortnames?: undefined; + }; }; }; diff --git a/apps/meteor/app/federation/server/handler/index.js b/apps/meteor/app/federation/server/handler/index.ts similarity index 80% rename from apps/meteor/app/federation/server/handler/index.js rename to apps/meteor/app/federation/server/handler/index.ts index c5b19856f19f..f7a3ae53ec29 100644 --- a/apps/meteor/app/federation/server/handler/index.js +++ b/apps/meteor/app/federation/server/handler/index.ts @@ -5,7 +5,7 @@ import { federationRequestToPeer } from '../lib/http'; import { isFederationEnabled } from '../lib/isFederationEnabled'; import { clientLogger } from '../lib/logger'; -export async function federationSearchUsers(query) { +export async function federationSearchUsers(query: string) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -23,7 +23,7 @@ export async function federationSearchUsers(query) { return users; } -export async function getUserByUsername(query) { +export async function getUserByUsername(query: string) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -41,7 +41,13 @@ export async function getUserByUsername(query) { return user; } -export async function requestEventsFromLatest(domain, fromDomain, contextType, contextQuery, latestEventIds) { +export async function requestEventsFromLatest( + domain: string, + fromDomain: string, + contextType: unknown, + contextQuery: unknown, + latestEventIds: unknown, +) { if (!isFederationEnabled()) { throw disabled('client.requestEventsFromLatest'); } @@ -64,7 +70,7 @@ export async function requestEventsFromLatest(domain, fromDomain, contextType, c }); } -export async function dispatchEvents(domains, events) { +export async function dispatchEvents(domains: string[], events: unknown[]) { if (!isFederationEnabled()) { throw disabled('client.dispatchEvents'); } @@ -80,11 +86,11 @@ export async function dispatchEvents(domains, events) { } } -export async function dispatchEvent(domains, event) { +export async function dispatchEvent(domains: string[], event: unknown) { await dispatchEvents([...new Set(domains)], [event]); } -export async function getUpload(domain, fileId) { +export async function getUpload(domain: string, fileId: string) { const { data: { upload, buffer }, } = await federationRequestToPeer('GET', domain, `/api/v1/federation.uploads?${qs.stringify({ upload_id: fileId })}`); diff --git a/apps/meteor/app/irc/server/irc-bridge/localHandlers/index.js b/apps/meteor/app/irc/server/irc-bridge/localHandlers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/irc-bridge/localHandlers/index.js rename to apps/meteor/app/irc/server/irc-bridge/localHandlers/index.ts diff --git a/apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.js b/apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.js rename to apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.ts diff --git a/apps/meteor/app/irc/server/servers/index.js b/apps/meteor/app/irc/server/servers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/servers/index.js rename to apps/meteor/app/irc/server/servers/index.ts diff --git a/apps/meteor/app/lib/client/OAuthProxy.js b/apps/meteor/app/lib/client/OAuthProxy.ts similarity index 86% rename from apps/meteor/app/lib/client/OAuthProxy.js rename to apps/meteor/app/lib/client/OAuthProxy.ts index a5035783c676..ec9143528fc9 100644 --- a/apps/meteor/app/lib/client/OAuthProxy.js +++ b/apps/meteor/app/lib/client/OAuthProxy.ts @@ -6,12 +6,12 @@ OAuth.launchLogin = ((func) => function (options) { const proxy = settings.get('Accounts_OAuth_Proxy_services').replace(/\s/g, '').split(','); if (proxy.includes(options.loginService)) { - const redirect_uri = options.loginUrl.match(/(&redirect_uri=)([^&]+|$)/)[2]; + const redirectUri = options.loginUrl.match(/(&redirect_uri=)([^&]+|$)/)?.[2]; options.loginUrl = options.loginUrl.replace( /(&redirect_uri=)([^&]+|$)/, `$1${encodeURIComponent(settings.get('Accounts_OAuth_Proxy_host'))}/oauth_redirect`, ); - options.loginUrl = options.loginUrl.replace(/(&state=)([^&]+|$)/, `$1${redirect_uri}!$2`); + options.loginUrl = options.loginUrl.replace(/(&state=)([^&]+|$)/, `$1${redirectUri}!$2`); options.loginUrl = `${settings.get('Accounts_OAuth_Proxy_host')}/redirect/${encodeURIComponent(options.loginUrl)}`; } diff --git a/apps/meteor/app/livechat/client/collections/LivechatInquiry.js b/apps/meteor/app/livechat/client/collections/LivechatInquiry.js deleted file mode 100644 index c43a9cb31ca5..000000000000 --- a/apps/meteor/app/livechat/client/collections/LivechatInquiry.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const LivechatInquiry = new Mongo.Collection(null); diff --git a/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts b/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts new file mode 100644 index 000000000000..16b9533d1649 --- /dev/null +++ b/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts @@ -0,0 +1,4 @@ +import type { ILivechatInquiryRecord } from '@rocket.chat/core-typings'; +import { Mongo } from 'meteor/mongo'; + +export const LivechatInquiry = new Mongo.Collection(null); diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts index 5ba2cf0d9791..c6a671e2883d 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts @@ -30,12 +30,12 @@ const events = { const invalidateRoomQueries = async (rid: string) => { await queryClient.invalidateQueries(['rooms', { reference: rid, type: 'l' }]); - await queryClient.removeQueries(['rooms', rid]); - await queryClient.removeQueries(['/v1/rooms.info', rid]); + queryClient.removeQueries(['rooms', rid]); + queryClient.removeQueries(['/v1/rooms.info', rid]); }; const removeInquiry = async (inquiry: ILivechatInquiryRecord) => { - await LivechatInquiry.remove(inquiry._id); + LivechatInquiry.remove(inquiry._id); return queryClient.invalidateQueries(['rooms', { reference: inquiry.rid, type: 'l' }]); }; diff --git a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js b/apps/meteor/app/slackbridge/client/slackbridge_import.client.ts similarity index 85% rename from apps/meteor/app/slackbridge/client/slackbridge_import.client.js rename to apps/meteor/app/slackbridge/client/slackbridge_import.client.ts index eebc07ddb72d..2138fc2a35f9 100644 --- a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js +++ b/apps/meteor/app/slackbridge/client/slackbridge_import.client.ts @@ -1,7 +1,7 @@ import { settings } from '../../settings/client'; import { slashCommands } from '../../utils/client/slashCommand'; -settings.onload('SlackBridge_Enabled', (key, value) => { +settings.onload('SlackBridge_Enabled', (_key, value) => { if (value) { slashCommands.add({ command: 'slackbridge-import', diff --git a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts index 70a2a6008e56..c3d10b531b6b 100644 --- a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts +++ b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts @@ -2,7 +2,7 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; -import { emojiParser } from '../../../emoji/client/emojiParser.js'; +import { emojiParser } from '../../../emoji/client/emojiParser'; import { filterMarkdown } from '../../../markdown/lib/markdown'; import { MentionsParser } from '../../../mentions/lib/MentionsParser'; import { Users } from '../../../models/client'; @@ -26,7 +26,7 @@ export function normalizeThreadTitle({ ...message }: Readonly) { userTemplate: ({ label }) => ` ${label} `, roomTemplate: ({ prefix, mention }) => `${prefix} ${mention} `, }); - const { html } = emojiParser({ html: filteredMessage }); + const html = emojiParser(filteredMessage); return instance.parse({ ...message, msg: filteredMessage, html }).html; } diff --git a/apps/meteor/app/ui-master/server/index.js b/apps/meteor/app/ui-master/server/index.ts similarity index 81% rename from apps/meteor/app/ui-master/server/index.js rename to apps/meteor/app/ui-master/server/index.ts index 2d4f3cc7de56..b4f15f211abc 100644 --- a/apps/meteor/app/ui-master/server/index.js +++ b/apps/meteor/app/ui-master/server/index.ts @@ -1,3 +1,4 @@ +import type { ISettingColor } from '@rocket.chat/core-typings'; import { Settings } from '@rocket.chat/models'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; @@ -15,11 +16,11 @@ export * from './inject'; Meteor.startup(() => { Tracker.autorun(() => { - const injections = Object.values(headInjections.all()); + const injections = Object.values(headInjections.all()).filter((injection): injection is NonNullable => !!injection); Inject.rawModHtml('headInjections', applyHeadInjections(injections)); }); - settings.watch('Default_Referrer_Policy', (value) => { + settings.watch('Default_Referrer_Policy', (value) => { if (!value) { return injectIntoHead('noreferrer', ''); } @@ -40,7 +41,7 @@ Meteor.startup(() => { ); } - settings.watch('Assets_SvgFavicon_Enable', (value) => { + settings.watch('Assets_SvgFavicon_Enable', (value) => { const standardFavicons = ` `; @@ -56,7 +57,7 @@ Meteor.startup(() => { } }); - settings.watch('theme-color-sidebar-background', (value) => { + settings.watch('theme-color-sidebar-background', (value) => { const escapedValue = escapeHTML(value); injectIntoHead( 'theme-color-sidebar-background', @@ -64,7 +65,7 @@ Meteor.startup(() => { ); }); - settings.watch('Site_Name', (value = 'Rocket.Chat') => { + settings.watch('Site_Name', (value = 'Rocket.Chat') => { const escapedValue = escapeHTML(value); injectIntoHead( 'Site_Name', @@ -74,7 +75,7 @@ Meteor.startup(() => { ); }); - settings.watch('Meta_language', (value = '') => { + settings.watch('Meta_language', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead( 'Meta_language', @@ -82,27 +83,27 @@ Meteor.startup(() => { ); }); - settings.watch('Meta_robots', (value = '') => { + settings.watch('Meta_robots', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_robots', ``); }); - settings.watch('Meta_msvalidate01', (value = '') => { + settings.watch('Meta_msvalidate01', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_msvalidate01', ``); }); - settings.watch('Meta_google-site-verification', (value = '') => { + settings.watch('Meta_google-site-verification', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_google-site-verification', ``); }); - settings.watch('Meta_fb_app_id', (value = '') => { + settings.watch('Meta_fb_app_id', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_fb_app_id', ``); }); - settings.watch('Meta_custom', (value = '') => { + settings.watch('Meta_custom', (value = '') => { injectIntoHead('Meta_custom', value); }); @@ -127,7 +128,7 @@ const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => { // const variables = RocketChat.models.Settings.findOne({_id:'theme-custom-variables'}, {fields: { value: 1}}); const colors = await Settings.find({ _id: /theme-color-rc/i }, { projection: { value: 1, editor: 1 } }).toArray(); const css = colors - .filter((color) => color && color.value) + .filter((color): color is ISettingColor => !!color?.value) .map(({ _id, value, editor }) => { if (editor === 'expression') { return `--${_id.replace('theme-color-', '')}: var(--${value});`; @@ -138,7 +139,7 @@ const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => { injectIntoBody('dynamic-variables', ``); }); -renderDynamicCssList(); +await renderDynamicCssList(); settings.watchByRegex(/theme-color-rc/i, renderDynamicCssList); @@ -160,4 +161,4 @@ injectIntoBody( `, ); -injectIntoBody('icons', await Assets.getTextAsync('public/icons.svg')); +injectIntoBody('icons', (await Assets.getTextAsync('public/icons.svg')) ?? ''); diff --git a/apps/meteor/app/ui-master/server/inject.ts b/apps/meteor/app/ui-master/server/inject.ts index 1e00a0e47433..47b63db4bb3f 100644 --- a/apps/meteor/app/ui-master/server/inject.ts +++ b/apps/meteor/app/ui-master/server/inject.ts @@ -16,7 +16,7 @@ type Injection = tag: string; }; -export const headInjections = new ReactiveDict(); +export const headInjections = new ReactiveDict>(); const callback: NextHandleFunction = (req, res, next) => { if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') { @@ -32,7 +32,7 @@ const callback: NextHandleFunction = (req, res, next) => { return; } - const injection = headInjections.get(pathname.replace(/^\//, '').split('_')[0]) as Injection | undefined; + const injection = headInjections.get(pathname.replace(/^\//, '').split('_')[0]); if (!injection || typeof injection === 'string') { next(); diff --git a/apps/meteor/app/webrtc/client/WebRTCClass.js b/apps/meteor/app/webrtc/client/WebRTCClass.ts similarity index 66% rename from apps/meteor/app/webrtc/client/WebRTCClass.js rename to apps/meteor/app/webrtc/client/WebRTCClass.ts index eb9772966575..6ce3b5cc442f 100644 --- a/apps/meteor/app/webrtc/client/WebRTCClass.js +++ b/apps/meteor/app/webrtc/client/WebRTCClass.ts @@ -1,3 +1,5 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; @@ -13,33 +15,128 @@ import { t } from '../../utils/lib/i18n'; import { WEB_RTC_EVENTS } from '../lib/constants'; import { ChromeScreenShare } from './screenShare'; -class WebRTCTransportClass extends Emitter { - constructor(webrtcInstance) { +// FIXME: there is a mix of obsolete definitions and incorrect field assignments + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface RTCPeerConnection { + /** @deprecated non-standard */ + createdAt: number; + /** @deprecated non-standard */ + remoteMedia: MediaStreamConstraints; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface RTCOfferOptions { + /** @deprecated non-standard */ + mandatory?: unknown; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaStream { + /** @deprecated non-standard */ + volume?: GainNode; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaStreamConstraints { + /** @deprecated non-standard */ + desktop?: boolean; + } + + /** @deprecated browser-specific global */ + const chrome: { + webstore: { + install(url: string, onSuccess: () => void, onError: (error: any) => void): void; + }; + }; + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + rocketchatscreenshare?: unknown; + audioContext?: AudioContext; + } +} + +type EventData, TType> = Extract< + StreamerCallbackArgs, + [type: TType, data: any] +>[1]; + +type StatusData = EventData<'notify-room', `${string}/webrtc`, 'status'>; +type CallData = EventData<'notify-room-users', `${string}/webrtc`, 'call'>; +type CandidateData = EventData<'notify-user', `${string}/webrtc`, 'candidate'>; +type DescriptionData = EventData<'notify-user', `${string}/webrtc`, 'description'>; +type JoinData = EventData<'notify-user', `${string}/webrtc`, 'join'>; + +type RemoteItem = { + id: string; + url: MediaStream; + state: RTCIceConnectionState; + stateText?: string; + connected?: boolean; +}; + +type RemoteConnection = { + id: string; + media: MediaStreamConstraints; +}; + +class WebRTCTransportClass extends Emitter<{ + status: StatusData; + call: CallData; + candidate: CandidateData; + description: DescriptionData; + join: JoinData; +}> { + public debug = false; + + constructor(public webrtcInstance: WebRTCClass) { super(); - this.debug = false; - this.webrtcInstance = webrtcInstance; sdk.stream('notify-room', [`${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { this.log('WebRTCTransportClass - onRoom', type, data); this.emit(type, data); }); } - log(...args) { + log(...args: unknown[]) { if (this.debug === true) { console.log(...args); } } - onUserStream(type, data) { + onUserStream(type: 'candidate', data: CandidateData): void; + + onUserStream(type: 'description', data: DescriptionData): void; + + onUserStream(type: 'join', data: JoinData): void; + + onUserStream( + ...[type, data]: + | [type: 'candidate', data: CandidateData] + | [type: 'description', data: DescriptionData] + | [type: 'join', data: JoinData] + ) { if (data.room !== this.webrtcInstance.room) { return; } this.log('WebRTCTransportClass - onUser', type, data); - this.emit(type, data); + + switch (type) { + case 'candidate': + this.emit('candidate', data); + break; + case 'description': + this.emit('description', data); + break; + case 'join': + this.emit('join', data); + break; + } } - startCall(data) { + startCall(data: CallData) { this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId); sdk.publish('notify-room-users', [ `${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`, @@ -53,7 +150,7 @@ class WebRTCTransportClass extends Emitter { ]); } - joinCall(data) { + joinCall(data: JoinData) { this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId); if (data.monitor === true) { sdk.publish('notify-user', [ @@ -80,75 +177,105 @@ class WebRTCTransportClass extends Emitter { } } - sendCandidate(data) { + sendCandidate(data: CandidateData) { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendCandidate', data); sdk.publish('notify-user', [`${data.to}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.CANDIDATE, data]); } - sendDescription(data) { + sendDescription(data: DescriptionData) { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendDescription', data); sdk.publish('notify-user', [`${data.to}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.DESCRIPTION, data]); } - sendStatus(data) { + sendStatus(data: StatusData) { this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room); data.from = this.webrtcInstance.selfId; sdk.publish('notify-room', [`${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.STATUS, data]); } - onRemoteCall(fn) { + onRemoteCall(fn: (data: CallData) => void) { return this.on(WEB_RTC_EVENTS.CALL, fn); } - onRemoteJoin(fn) { + onRemoteJoin(fn: (data: JoinData) => void) { return this.on(WEB_RTC_EVENTS.JOIN, fn); } - onRemoteCandidate(fn) { + onRemoteCandidate(fn: (data: CandidateData) => void) { return this.on(WEB_RTC_EVENTS.CANDIDATE, fn); } - onRemoteDescription(fn) { + onRemoteDescription(fn: (data: DescriptionData) => void) { return this.on(WEB_RTC_EVENTS.DESCRIPTION, fn); } - onRemoteStatus(fn) { + onRemoteStatus(fn: (data: StatusData) => void) { return this.on(WEB_RTC_EVENTS.STATUS, fn); } } class WebRTCClass { - /* - @param seldId {String} - @param room {String} - */ + transport: WebRTCTransportClass; + + config: { iceServers: RTCIceServer[] }; + + debug: boolean; + + TransportClass: typeof WebRTCTransportClass; + + peerConnections: Record = {}; + + remoteItems: ReactiveVar; + + remoteItemsById: ReactiveVar>; + + callInProgress: ReactiveVar; + + audioEnabled: ReactiveVar; + + videoEnabled: ReactiveVar; + + overlayEnabled: ReactiveVar; + + screenShareEnabled: ReactiveVar; + + localUrl: ReactiveVar; - constructor(selfId, room, autoAccept = false) { + active: boolean; + + remoteMonitoring: boolean; + + monitor: boolean; + + navigator: string | undefined; + + screenShareAvailable: boolean; + + media: MediaStreamConstraints; + + constructor(public selfId: string, public room: string, public autoAccept = false) { this.config = { iceServers: [], }; this.debug = false; this.TransportClass = WebRTCTransportClass; - this.selfId = selfId; - this.room = room; - let servers = settings.get('WebRTC_Servers'); + let servers = settings.get('WebRTC_Servers'); if (servers && servers.trim() !== '') { servers = servers.replace(/\s/g, ''); - servers = servers.split(','); - servers.forEach((server) => { - server = server.split('@'); - const serverConfig = { - urls: server.pop(), + servers.split(',').forEach((server) => { + const parts = server.split('@'); + const serverConfig: RTCIceServer = { + urls: parts.pop()!, }; - if (server.length === 1) { - server = server[0].split(':'); - serverConfig.username = decodeURIComponent(server[0]); - serverConfig.credential = decodeURIComponent(server[1]); + if (parts.length === 1) { + const [username, credential] = parts[0].split(':'); + serverConfig.username = decodeURIComponent(username); + serverConfig.credential = decodeURIComponent(credential); } this.config.iceServers.push(serverConfig); }); @@ -161,11 +288,10 @@ class WebRTCClass { this.videoEnabled = new ReactiveVar(false); this.overlayEnabled = new ReactiveVar(false); this.screenShareEnabled = new ReactiveVar(false); - this.localUrl = new ReactiveVar(); + this.localUrl = new ReactiveVar(undefined); this.active = false; this.remoteMonitoring = false; this.monitor = false; - this.autoAccept = autoAccept; this.navigator = undefined; const userAgent = navigator.userAgent.toLocaleLowerCase(); @@ -179,7 +305,7 @@ class WebRTCClass { this.navigator = 'safari'; } - this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator); + this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator!); this.media = { video: true, audio: true, @@ -194,18 +320,41 @@ class WebRTCClass { setInterval(this.checkPeerConnections.bind(this), 1000); } - onUserStream(...args) { - return this.transport.onUserStream(...args); + onUserStream(type: 'candidate', data: CandidateData): void; + + onUserStream(type: 'description', data: DescriptionData): void; + + onUserStream(type: 'join', data: JoinData): void; + + onUserStream( + ...[type, data]: + | [type: 'candidate', data: CandidateData] + | [type: 'description', data: DescriptionData] + | [type: 'join', data: JoinData] + ) { + switch (type) { + case 'candidate': + this.transport.onUserStream('candidate', data); + break; + + case 'description': + this.transport.onUserStream('description', data); + break; + + case 'join': + this.transport.onUserStream('join', data); + break; + } } - log(...args) { + log(...args: unknown[]) { if (this.debug === true) { - console.log.apply(console, args); + console.log(...args); } } - onError(...args) { - console.error.apply(console, args); + onError(...args: unknown[]) { + console.error(...args); } checkPeerConnections() { @@ -221,13 +370,13 @@ class WebRTCClass { } updateRemoteItems() { - const items = []; - const itemsById = {}; + const items: RemoteItem[] = []; + const itemsById: Record = {}; const { peerConnections } = this; Object.entries(peerConnections).forEach(([id, peerConnection]) => { peerConnection.getRemoteStreams().forEach((remoteStream) => { - const item = { + const item: RemoteItem = { id, url: remoteStream, state: peerConnection.iceConnectionState, @@ -266,9 +415,9 @@ class WebRTCClass { if (this.active !== true || this.monitor === true || this.remoteMonitoring === true) { return; } - const remoteConnections = []; + const remoteConnections: RemoteConnection[] = []; const { peerConnections } = this; - Object.keys(peerConnections).entries(([id, { remoteMedia: media }]) => { + Object.entries(peerConnections).forEach(([id, { remoteMedia: media }]) => { remoteConnections.push({ id, media, @@ -281,16 +430,9 @@ class WebRTCClass { }); } - /* - @param data {Object} - from {String} - media {Object} - remoteConnections {Array[Object]} - id {String} - media {Object} - */ + callInProgressTimeout: ReturnType | undefined = undefined; - onRemoteStatus(data) { + onRemoteStatus(data: StatusData) { // this.log(onRemoteStatus, arguments); this.callInProgress.set(true); clearTimeout(this.callInProgressTimeout); @@ -300,7 +442,7 @@ class WebRTCClass { } const remoteConnections = [ { - id: data.from, + id: data.from!, media: data.media, }, ...data.remoteConnections, @@ -317,11 +459,7 @@ class WebRTCClass { }); } - /* - @param id {String} - */ - - getPeerConnection(id) { + getPeerConnection(id: string) { if (this.peerConnections[id] != null) { return this.peerConnections[id]; } @@ -386,8 +524,10 @@ class WebRTCClass { return peerConnection; } - _getUserMedia(media, onSuccess, onError) { - const onSuccessLocal = (stream) => { + audioContext: AudioContext | undefined; + + _getUserMedia(media: MediaStreamConstraints, onSuccess: (stream: MediaStream) => void, onError: (error?: any) => void) { + const onSuccessLocal = (stream: MediaStream) => { if (AudioContext && stream.getAudioTracks().length > 0) { const audioContext = new AudioContext(); const source = audioContext.createMediaStreamSource(stream); @@ -403,23 +543,24 @@ class WebRTCClass { } onSuccess(stream); }; - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + + if (navigator.mediaDevices?.getUserMedia) { return navigator.mediaDevices.getUserMedia(media).then(onSuccessLocal).catch(onError); } - navigator.getUserMedia(media, onSuccessLocal, onError); + navigator.getUserMedia?.(media, onSuccessLocal, onError); } - getUserMedia(media, onSuccess, onError = this.onError) { + getUserMedia(media: MediaStreamConstraints, onSuccess: (stream: MediaStream) => void, onError: (error: any) => void = this.onError) { if (media.desktop !== true) { - this._getUserMedia(media, onSuccess, onError); + void this._getUserMedia(media, onSuccess, onError); return; } if (this.screenShareAvailable !== true) { console.log('Screen share is not avaliable'); return; } - const getScreen = (audioStream) => { + const getScreen = (audioStream?: MediaStream) => { const refresh = function () { imperativeModal.open({ component: GenericModal, @@ -466,7 +607,7 @@ class WebRTCClass { return onError(false); } - const getScreenSuccess = (stream) => { + const getScreenSuccess = (stream: MediaStream) => { if (audioStream != null) { stream.addTrack(audioStream.getAudioTracks()[0]); } @@ -480,9 +621,9 @@ class WebRTCClass { mediaSource: 'window', }, }; - this._getUserMedia(media, getScreenSuccess, onError); + void this._getUserMedia(media, getScreenSuccess, onError); } else { - ChromeScreenShare.getSourceId(this.navigator, (id) => { + ChromeScreenShare.getSourceId(this.navigator!, (id) => { media = { audio: false, video: { @@ -494,21 +635,21 @@ class WebRTCClass { }, }, }; - this._getUserMedia(media, getScreenSuccess, onError); + void this._getUserMedia(media, getScreenSuccess, onError); }); } }; if (this.navigator === 'firefox' || media.audio == null || media.audio === false) { getScreen(); } else { - const getAudioSuccess = (audioStream) => { + const getAudioSuccess = (audioStream: MediaStream) => { getScreen(audioStream); }; const getAudioError = () => { getScreen(); }; - this._getUserMedia( + void this._getUserMedia( { audio: media.audio, }, @@ -518,37 +659,29 @@ class WebRTCClass { } } - /* - @param callback {Function} - */ - - getLocalUserMedia(callback, ...args) { + getLocalUserMedia(callback: (...args: any[]) => void, ...args: unknown[]) { this.log('getLocalUserMedia', [callback, ...args]); if (this.localStream != null) { return callback(null, this.localStream); } - const onSuccess = (stream) => { + const onSuccess = (stream: MediaStream) => { this.localStream = stream; !this.audioEnabled.get() && this.disableAudio(); !this.videoEnabled.get() && this.disableVideo(); this.localUrl.set(stream); const { peerConnections } = this; Object.entries(peerConnections).forEach(([, peerConnection]) => peerConnection.addStream(stream)); - document.querySelector('video#localVideo').srcObject = stream; + document.querySelector('video#localVideo')!.srcObject = stream; callback(null, this.localStream); }; - const onError = (error) => { + const onError = (error: any) => { callback(false); this.onError(error); }; this.getUserMedia(this.media, onSuccess, onError); } - /* - @param id {String} - */ - - stopPeerConnection = (id) => { + stopPeerConnection = (id: string) => { const peerConnection = this.peerConnections[id]; if (peerConnection == null) { return; @@ -563,7 +696,7 @@ class WebRTCClass { Object.keys(peerConnections).forEach(this.stopPeerConnection); - window.audioContext && window.audioContext.close(); + void window.audioContext?.close(); // FIXME: probably should be `this.audioContext` } setAudioEnabled(enabled = true) { @@ -590,6 +723,8 @@ class WebRTCClass { return this.enableAudio(); } + localStream: MediaStream | undefined; + setVideoEnabled(enabled = true) { if (this.localStream != null) { this.localStream.getVideoTracks().forEach((video) => { @@ -649,13 +784,7 @@ class WebRTCClass { this.stopAllPeerConnections(); } - /* - @param media {Object} - audio {Boolean} - video {Boolean} - */ - - startCall(media = {}, ...args) { + startCall(media: MediaStreamConstraints = {}, ...args: unknown[]) { this.log('startCall', [media, ...args]); this.media = media; this.getLocalUserMedia(() => { @@ -666,7 +795,7 @@ class WebRTCClass { }); } - startCallAsMonitor(media = {}, ...args) { + startCallAsMonitor(media: MediaStreamConstraints = {}, ...args: unknown[]) { this.log('startCallAsMonitor', [media, ...args]); this.media = media; this.active = true; @@ -677,16 +806,7 @@ class WebRTCClass { }); } - /* - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - */ - - onRemoteCall(data) { + onRemoteCall(data: CallData) { if (this.autoAccept === true) { setTimeout(() => { this.joinCall({ @@ -700,31 +820,31 @@ class WebRTCClass { const user = Meteor.users.findOne(data.from); let fromUsername = undefined; - if (user && user.username) { + if (user?.username) { fromUsername = user.username; } const subscription = ChatSubscription.findOne({ rid: data.room, - }); + })!; let icon; let title; if (data.monitor === true) { - icon = 'eye'; + icon = 'eye' as const; title = t('WebRTC_monitor_call_from_%s', fromUsername); } else if (subscription && subscription.t === 'd') { - if (data.media && data.media.video) { - icon = 'videocam'; + if (data.media?.video) { + icon = 'video' as const; title = t('WebRTC_direct_video_call_from_%s', fromUsername); } else { - icon = 'phone'; + icon = 'phone' as const; title = t('WebRTC_direct_audio_call_from_%s', fromUsername); } - } else if (data.media && data.media.video) { - icon = 'videocam'; + } else if (data.media?.video) { + icon = 'video' as const; title = t('WebRTC_group_video_call_from_%s', subscription.name); } else { - icon = 'phone'; + icon = 'phone' as const; title = t('WebRTC_group_audio_call_from_%s', subscription.name); } @@ -737,7 +857,7 @@ class WebRTCClass { cancelText: t('No'), children: t('Do_you_want_to_accept'), onConfirm: () => { - goToRoomById(data.room); + void goToRoomById(data.room!); return this.joinCall({ to: data.from, monitor: data.monitor, @@ -750,32 +870,22 @@ class WebRTCClass { }); } - /* - @param data {Object} - to {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - */ - - joinCall(data = {}, ...args) { + joinCall(data: JoinData = {}, ...args: unknown[]) { data.media = this.media; this.log('joinCall', [data, ...args]); this.getLocalUserMedia(() => { - this.remoteMonitoring = data.monitor; + this.remoteMonitoring = data.monitor!; this.active = true; this.transport.joinCall(data); }); } - onRemoteJoin(data, ...args) { + onRemoteJoin(data: JoinData, ...args: unknown[]) { if (this.active !== true) { return; } this.log('onRemoteJoin', [data, ...args]); - let peerConnection = this.getPeerConnection(data.from); + let peerConnection = this.getPeerConnection(data.from!); // needsRefresh = false // if peerConnection.iceConnectionState isnt 'new' @@ -785,18 +895,18 @@ class WebRTCClass { // # if peerConnection.signalingState is "have-local-offer" or needsRefresh - if (peerConnection.signalingState !== 'checking') { - this.stopPeerConnection(data.from); - peerConnection = this.getPeerConnection(data.from); + if ((peerConnection.signalingState as RTCSignalingState | 'checking') !== 'checking') { + this.stopPeerConnection(data.from!); + peerConnection = this.getPeerConnection(data.from!); } if (peerConnection.iceConnectionState !== 'new') { return; } - peerConnection.remoteMedia = data.media; + peerConnection.remoteMedia = data.media!; if (this.localStream) { peerConnection.addStream(this.localStream); } - const onOffer = (offer) => { + const onOffer: RTCSessionDescriptionCallback = (offer) => { const onLocalDescription = () => { this.transport.sendDescription({ to: data.from, @@ -810,39 +920,39 @@ class WebRTCClass { }); }; - peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); + void peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); }; if (data.monitor === true) { - peerConnection.createOffer(onOffer, this.onError, { + void peerConnection.createOffer(onOffer, this.onError, { mandatory: { - OfferToReceiveAudio: data.media.audio, - OfferToReceiveVideo: data.media.video, + OfferToReceiveAudio: data.media?.audio, + OfferToReceiveVideo: data.media?.video, }, }); } else { - peerConnection.createOffer(onOffer, this.onError); + void peerConnection.createOffer(onOffer, this.onError); } } - onRemoteOffer(data, ...args) { + onRemoteOffer(data: Omit, ...args: unknown[]) { if (this.active !== true) { return; } this.log('onRemoteOffer', [data, ...args]); - let peerConnection = this.getPeerConnection(data.from); + let peerConnection = this.getPeerConnection(data.from!); if (['have-local-offer', 'stable'].includes(peerConnection.signalingState) && peerConnection.createdAt < data.ts) { - this.stopPeerConnection(data.from); - peerConnection = this.getPeerConnection(data.from); + this.stopPeerConnection(data.from!); + peerConnection = this.getPeerConnection(data.from!); } if (peerConnection.iceConnectionState !== 'new') { return; } - peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + void peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); try { if (this.localStream) { @@ -852,7 +962,7 @@ class WebRTCClass { console.log(error); } - const onAnswer = (answer) => { + const onAnswer: RTCSessionDescriptionCallback = (answer) => { const onLocalDescription = () => { this.transport.sendDescription({ to: data.from, @@ -865,20 +975,13 @@ class WebRTCClass { }); }; - peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); + void peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); }; - peerConnection.createAnswer(onAnswer, this.onError); + void peerConnection.createAnswer(onAnswer, this.onError); } - /* - @param data {Object} - to {String} - from {String} - candidate {RTCIceCandidate JSON encoded} - */ - - onRemoteCandidate(data, ...args) { + onRemoteCandidate(data: CandidateData, ...args: unknown[]) { if (this.active !== true) { return; } @@ -886,32 +989,19 @@ class WebRTCClass { return; } this.log('onRemoteCandidate', [data, ...args]); - const peerConnection = this.getPeerConnection(data.from); + const peerConnection = this.getPeerConnection(data.from!); if ( peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed' ) { - peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); + void peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); } - document.querySelector('video#remoteVideo').srcObject = this.remoteItems.get()[0]?.url; + document.querySelector('video#remoteVideo')!.srcObject = this.remoteItems.get()[0]?.url; } - /* - @param data {Object} - to {String} - from {String} - type {String} [offer, answer] - description {RTCSessionDescription JSON encoded} - ts {Integer} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - */ - - onRemoteDescription(data, ...args) { + onRemoteDescription(data: DescriptionData, ...args: unknown[]) { if (this.active !== true) { return; } @@ -919,7 +1009,7 @@ class WebRTCClass { return; } this.log('onRemoteDescription', [data, ...args]); - const peerConnection = this.getPeerConnection(data.from); + const peerConnection = this.getPeerConnection(data.from!); if (data.type === 'offer') { peerConnection.remoteMedia = data.media; this.onRemoteOffer({ @@ -928,17 +1018,19 @@ class WebRTCClass { description: data.description, }); } else { - peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + void peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); } } } const WebRTC = new (class { + instancesByRoomId: Record = {}; + constructor() { this.instancesByRoomId = {}; } - getInstanceByRoomId(rid, visitorId = null) { + getInstanceByRoomId(rid: IRoom['_id'], visitorId: string | null = null) { let enabled = false; if (!visitorId) { const subscription = ChatSubscription.findOne({ rid }); @@ -956,17 +1048,17 @@ const WebRTC = new (class { enabled = settings.get('WebRTC_Enable_Channel'); break; case 'l': - enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; } } else { - enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; } enabled = enabled && settings.get('WebRTC_Enabled'); if (enabled === false) { return; } if (this.instancesByRoomId[rid] == null) { - let uid = Meteor.userId(); + let uid = Meteor.userId()!; let autoAccept = false; if (visitorId) { uid = visitorId; @@ -980,13 +1072,26 @@ const WebRTC = new (class { Meteor.startup(() => { Tracker.autorun(() => { - if (Meteor.userId()) { - sdk.stream('notify-user', [`${Meteor.userId()}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { + const uid = Meteor.userId(); + + if (uid) { + sdk.stream('notify-user', [`${uid}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { if (data.room == null) { return; } const webrtc = WebRTC.getInstanceByRoomId(data.room); - webrtc.onUserStream(type, data); + + switch (type) { + case 'candidate': + webrtc?.onUserStream('candidate', data); + break; + case 'description': + webrtc?.onUserStream('description', data); + break; + case 'join': + webrtc?.onUserStream('join', data); + break; + } }); } }); diff --git a/apps/meteor/app/webrtc/client/adapter.js b/apps/meteor/app/webrtc/client/adapter.js deleted file mode 100644 index 972e68e09f3c..000000000000 --- a/apps/meteor/app/webrtc/client/adapter.js +++ /dev/null @@ -1,6 +0,0 @@ -window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; -window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; -window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate; -window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; -window.AudioContext = window.AudioContext || window.mozAudioContext || window.webkitAudioContext; -navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; diff --git a/apps/meteor/app/webrtc/client/adapter.ts b/apps/meteor/app/webrtc/client/adapter.ts new file mode 100644 index 000000000000..f98ae7815c05 --- /dev/null +++ b/apps/meteor/app/webrtc/client/adapter.ts @@ -0,0 +1,7 @@ +// FIXME: probably outdated +window.RTCPeerConnection = window.RTCPeerConnection ?? window.mozRTCPeerConnection ?? window.webkitRTCPeerConnection; +window.RTCSessionDescription = window.RTCSessionDescription ?? window.mozRTCSessionDescription ?? window.webkitRTCSessionDescription; +window.RTCIceCandidate = window.RTCIceCandidate ?? window.mozRTCIceCandidate ?? window.webkitRTCIceCandidate; +window.RTCSessionDescription = window.RTCSessionDescription ?? window.mozRTCSessionDescription ?? window.webkitRTCSessionDescription; +window.AudioContext = window.AudioContext ?? window.mozAudioContext ?? window.webkitAudioContext; +navigator.getUserMedia = navigator.getUserMedia ?? navigator.mozGetUserMedia ?? navigator.webkitGetUserMedia; diff --git a/apps/meteor/app/webrtc/client/screenShare.js b/apps/meteor/app/webrtc/client/screenShare.ts similarity index 77% rename from apps/meteor/app/webrtc/client/screenShare.js rename to apps/meteor/app/webrtc/client/screenShare.ts index ecb6f93a51d0..3fac4a05bfea 100644 --- a/apps/meteor/app/webrtc/client/screenShare.js +++ b/apps/meteor/app/webrtc/client/screenShare.ts @@ -1,18 +1,20 @@ import { fireGlobalEvent } from '../../../client/lib/utils/fireGlobalEvent'; export const ChromeScreenShare = { - callbacks: {}, - installed: false, - init() { - this.callbacks['get-RocketChatScreenSharingExtensionVersion'] = (version) => { + callbacks: { + 'get-RocketChatScreenSharingExtensionVersion': (version: unknown) => { if (version) { - this.installed = true; + ChromeScreenShare.installed = true; } - }; + }, + 'getSourceId': (_sourceId: string): void => undefined, + }, + installed: false, + init() { window.postMessage('get-RocketChatScreenSharingExtensionVersion', '*'); }, - getSourceId(navigator, callback) { - if (callback == null) { + getSourceId(navigator: string, callback: (sourceId: string) => void) { + if (!callback) { throw new Error('"callback" parameter is mandatory.'); } this.callbacks.getSourceId = callback; @@ -36,8 +38,7 @@ window.addEventListener('message', (e) => { throw new Error('PermissionDeniedError'); } if (e.data.version != null) { - ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion'] && - ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion'](e.data.version); + ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion']?.(e.data.version); } else if (e.data.sourceId != null) { return typeof ChromeScreenShare.callbacks.getSourceId === 'function' && ChromeScreenShare.callbacks.getSourceId(e.data.sourceId); } diff --git a/apps/meteor/client/components/MarkdownText.tsx b/apps/meteor/client/components/MarkdownText.tsx index a116ad83ddd9..9ce44f8a9ff4 100644 --- a/apps/meteor/client/components/MarkdownText.tsx +++ b/apps/meteor/client/components/MarkdownText.tsx @@ -123,7 +123,7 @@ const MarkdownText = ({ // We are using the old emoji parser here. This could come // with additional processing use, but is the workaround available right now. // Should be replaced in the future with the new parser. - return renderMessageEmoji({ html: markedHtml }); + return renderMessageEmoji(markedHtml); } return markedHtml; diff --git a/apps/meteor/client/definitions/global.d.ts b/apps/meteor/client/definitions/global.d.ts index 8b20108e8e48..0916ef237119 100644 --- a/apps/meteor/client/definitions/global.d.ts +++ b/apps/meteor/client/definitions/global.d.ts @@ -4,5 +4,80 @@ declare global { // eslint-disable-next-line @typescript-eslint/naming-convention interface Window { RocketChatDesktop?: IRocketChatDesktop; + + /** @deprecated use `window.RTCPeerConnection` */ + mozRTCPeerConnection?: RTCPeerConnection; + /** @deprecated use `window.RTCPeerConnection` */ + webkitRTCPeerConnection?: RTCPeerConnection; + + /** @deprecated use `window.RTCSessionDescription` */ + mozRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCSessionDescription` */ + webkitRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCIceCandidate` */ + mozRTCIceCandidate?: RTCIceCandidate; + /** @deprecated use `window.RTCIceCandidate` */ + webkitRTCIceCandidate?: RTCIceCandidate; + /** @deprecated use `window.RTCSessionDescription` */ + mozRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCSessionDescription` */ + webkitRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.AudioContext` */ + mozAudioContext?: AudioContext; + /** @deprecated use `window.AudioContext` */ + webkitAudioContext?: AudioContext; + } + + interface Navigator { + /** @deprecated */ + getUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + webkitGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + mozGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + msGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + } + + interface RTCPeerConnection { + /** @deprecated use `getReceivers() */ + getRemoteStreams(): MediaStream[]; + /** @deprecated */ + addStream(stream: MediaStream): void; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaTrackConstraints { + /** @deprecated */ + mozMediaSource?: string; + /** @deprecated */ + mediaSource?: string; + /** @deprecated */ + mandatory?: { + chromeMediaSource: string; + chromeMediaSourceId: string; + maxWidth: number; + maxHeight: number; + }; } } diff --git a/apps/meteor/client/lib/utils/renderMessageEmoji.ts b/apps/meteor/client/lib/utils/renderMessageEmoji.ts index 7960ec1914e5..644187605ae7 100644 --- a/apps/meteor/client/lib/utils/renderMessageEmoji.ts +++ b/apps/meteor/client/lib/utils/renderMessageEmoji.ts @@ -1,3 +1,3 @@ import { emojiParser } from '../../../app/emoji/client/emojiParser'; -export const renderMessageEmoji = ({ html }: { html: string }): string => emojiParser({ html }).html; +export const renderMessageEmoji = (html: string) => emojiParser(html); diff --git a/apps/meteor/client/providers/OmnichannelProvider.tsx b/apps/meteor/client/providers/OmnichannelProvider.tsx index 9517a9d3b155..6e3c65610709 100644 --- a/apps/meteor/client/providers/OmnichannelProvider.tsx +++ b/apps/meteor/client/providers/OmnichannelProvider.tsx @@ -1,8 +1,9 @@ -import type { - IOmnichannelAgent, - OmichannelRoutingConfig, - OmnichannelSortingMechanismSettingType, - ILivechatInquiryRecord, +import { + type IOmnichannelAgent, + type OmichannelRoutingConfig, + type OmnichannelSortingMechanismSettingType, + type ILivechatInquiryRecord, + LivechatInquiryStatus, } from '@rocket.chat/core-typings'; import { useSafely } from '@rocket.chat/fuselage-hooks'; import { useUser, useSetting, usePermission, useMethod, useEndpoint, useStream } from '@rocket.chat/ui-contexts'; @@ -137,7 +138,7 @@ const OmnichannelProvider = ({ children }: OmnichannelProviderProps) => { } return LivechatInquiry.find( - { status: 'queued' }, + { status: LivechatInquiryStatus.QUEUED }, { sort: getOmniChatSortQuery(omnichannelSortingMechanism), limit: omnichannelPoolMaxIncoming, diff --git a/apps/meteor/client/views/meet/CallPage.tsx b/apps/meteor/client/views/meet/CallPage.tsx index d07e3dfd6831..97b5c93c3ded 100644 --- a/apps/meteor/client/views/meet/CallPage.tsx +++ b/apps/meteor/client/views/meet/CallPage.tsx @@ -60,35 +60,47 @@ const CallPage = ({ if (!visitorId) { throw new Error('Call Page - no visitor id'); } - const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId as any); + const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId); const isMobileDevice = (): boolean => { if (isLayoutEmbedded) { setCallInIframe(true); } if (window.innerWidth <= 450 && window.innerHeight >= 629 && window.innerHeight <= 900) { setIsLocalMobileDevice(true); - webrtcInstance.media = { - audio: true, - video: { - width: { ideal: 440 }, - height: { ideal: 580 }, - }, - }; + if (webrtcInstance) + webrtcInstance.media = { + audio: true, + video: { + width: { ideal: 440 }, + height: { ideal: 580 }, + }, + }; return true; } return false; }; - const unsubNotifyUser = subscribeNotifyUser(`${visitorId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type: any, data: any) => { + const unsubNotifyUser = subscribeNotifyUser(`${visitorId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type, data) => { if (data.room == null) { return; } - webrtcInstance.onUserStream(type, data); + + switch (type) { + case 'candidate': + webrtcInstance?.onUserStream('candidate', data); + break; + case 'description': + webrtcInstance?.onUserStream('description', data); + break; + case 'join': + webrtcInstance?.onUserStream('join', data); + break; + } }); const unsubNotifyRoom = subscribeNotifyRoom(`${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type: any, data: any) => { if (type === 'callStatus' && data.callStatus === 'ended') { - webrtcInstance.stop(); + webrtcInstance?.stop(); setStatus(data.callStatus); } else if (type === 'getDeviceType') { sdk.publish('notify-room', [ @@ -130,7 +142,7 @@ const CallPage = ({ if (status === 'inProgress') { sdk.publish('notify-room', [`${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, 'getDeviceType']); - webrtcInstance.startCall({ + webrtcInstance?.startCall({ audio: true, video: { width: { ideal: 1920 }, @@ -145,10 +157,10 @@ const CallPage = ({ if (type === 'callStatus') { switch (data.callStatus) { case 'ended': - webrtcInstance.stop(); + webrtcInstance?.stop(); break; case 'inProgress': - webrtcInstance.startCall({ + webrtcInstance?.startCall({ audio: true, video: { width: { ideal: 1920 }, @@ -168,10 +180,10 @@ const CallPage = ({ const toggleButton = (control: any): any => { if (control === 'mic') { - WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleAudio(); + WebRTC.getInstanceByRoomId(roomId, visitorToken)?.toggleAudio(); return setIsMicOn(!isMicOn); } - WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleVideo(); + WebRTC.getInstanceByRoomId(roomId, visitorToken)?.toggleVideo(); setIsCameraOn(!isCameraOn); sdk.publish('notify-room', [ `${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, diff --git a/apps/meteor/definition/externals/global.d.ts b/apps/meteor/definition/externals/global.d.ts index 52e9aa4b1f31..94ce146ae4d2 100644 --- a/apps/meteor/definition/externals/global.d.ts +++ b/apps/meteor/definition/externals/global.d.ts @@ -5,30 +5,6 @@ declare global { interface Navigator { /** @deprecated */ readonly userLanguage?: string; - getUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - webkitGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - mozGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - msGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; } const __meteor_runtime_config__: { diff --git a/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts b/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts index b0498aaf4a0d..df6118689a0d 100644 --- a/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts +++ b/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts @@ -1,5 +1,6 @@ declare module 'meteor/meteorhacks:inject-initial' { namespace Inject { function rawBody(key: string, value: string): void; + function rawModHtml(key: string, value: (html: string) => string): void; } } diff --git a/apps/meteor/ee/server/apps/storage/index.js b/apps/meteor/ee/server/apps/storage/index.ts similarity index 100% rename from apps/meteor/ee/server/apps/storage/index.js rename to apps/meteor/ee/server/apps/storage/index.ts diff --git a/apps/meteor/package.json b/apps/meteor/package.json index b7e7aab91573..585fefde1795 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -91,6 +91,7 @@ "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", "@types/busboy": "^1.5.4", + "@types/bytebuffer": "~5.0.49", "@types/chai": "~4.3.19", "@types/chai-as-promised": "^7.1.8", "@types/chai-datetime": "0.0.39", diff --git a/packages/base64/src/base64.ts b/packages/base64/src/base64.ts index c35c76254a10..8cc2ec80bc64 100644 --- a/packages/base64/src/base64.ts +++ b/packages/base64/src/base64.ts @@ -11,23 +11,7 @@ for (let i = 0; i < BASE_64_CHARS.length; i++) { BASE_64_VALS[getChar(i)] = i; } -// XXX This is a weird place for this to live, but it's used both by -// this package and 'ejson', and we can't put it in 'ejson' without -// introducing a circular dependency. It should probably be in its own -// package or as a helper in a package that both 'base64' and 'ejson' -// use. -const newBinary = (len: number) => { - if (typeof Uint8Array === 'undefined' || typeof ArrayBuffer === 'undefined') { - const ret = Object.assign( - Array.from({ length: len }, () => 0), - { - $Uint8ArrayPolyfill: true, - }, - ); - return ret; - } - return new Uint8Array(new ArrayBuffer(len)); -}; +const newBinary = (len: number) => new Uint8Array(new ArrayBuffer(len)); const encode = (array: ArrayLike | string) => { if (typeof array === 'string') { diff --git a/packages/ddp-client/src/types/streams.ts b/packages/ddp-client/src/types/streams.ts index 99aa9b77c7c9..122d37c9f539 100644 --- a/packages/ddp-client/src/types/streams.ts +++ b/packages/ddp-client/src/types/streams.ts @@ -64,7 +64,22 @@ export interface StreamerEvents { { key: `${string}/videoconf`; args: [id: string] }, { key: `${string}/messagesRead`; args: [{ until: Date; tmid?: string }] }, { key: `${string}/messagesImported`; args: [null] }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: [ + type: 'status', + data: { + from?: string; + room?: string; + to?: string; + media: MediaStreamConstraints; + remoteConnections: { + id: string; + media: MediaStreamConstraints; + }[]; + }, + ]; + }, /* @deprecated over videoconf*/ // { key: `${string}/${string}`; args: [id: string] }, ]; @@ -173,7 +188,51 @@ export interface StreamerEvents { { key: `${string}/userData`; args: [IUserDataEvent] }, { key: `${string}/updateInvites`; args: [unknown] }, { key: `${string}/departmentAgentData`; args: [unknown] }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: + | [ + type: 'candidate', + data: { + from?: string; + room?: string; + to?: string; + candidate: RTCIceCandidateInit; + }, + ] + | [ + type: 'description', + data: + | { + from?: string; + room?: string; + to?: string; + type: 'offer'; + ts: number; + media: MediaStreamConstraints; + description: RTCSessionDescriptionInit; + } + | { + from?: string; + room?: string; + to?: string; + type: 'answer'; + ts: number; + media?: undefined; + description: RTCSessionDescriptionInit; + }, + ] + | [ + type: 'join', + data: { + from?: string; + room?: string; + to?: string; + media?: MediaStreamConstraints; + monitor?: boolean; + }, + ]; + }, { key: `${string}/otr`; args: [ @@ -283,7 +342,19 @@ export interface StreamerEvents { key: `${string}/video-conference`; args: [{ action: string; params: { callId: VideoConference['_id']; uid: IUser['_id']; rid: IRoom['_id'] } }]; }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: [ + type: 'call', + data: { + from?: string; + room?: string; + to?: string; + media: MediaStreamConstraints; + monitor?: boolean; + }, + ]; + }, { key: `${string}/otr`; args: [ diff --git a/yarn.lock b/yarn.lock index 19d9ff0d5bb1..663944e2b63f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9002,6 +9002,7 @@ __metadata: "@types/bcrypt": "npm:^5.0.2" "@types/body-parser": "npm:^1.19.5" "@types/busboy": "npm:^1.5.4" + "@types/bytebuffer": "npm:~5.0.49" "@types/chai": "npm:~4.3.19" "@types/chai-as-promised": "npm:^7.1.8" "@types/chai-datetime": "npm:0.0.39" @@ -11539,6 +11540,16 @@ __metadata: languageName: node linkType: hard +"@types/bytebuffer@npm:~5.0.49": + version: 5.0.49 + resolution: "@types/bytebuffer@npm:5.0.49" + dependencies: + "@types/long": "npm:^3.0.0" + "@types/node": "npm:*" + checksum: 10/31eb2521d2710f256c3d17a3e8d87f04394f335b29f7276c31c054ddbf4795146f2663effa3b6e910442da69238e994d2db9f7d5918eead4313e3f9e29165932 + languageName: node + linkType: hard + "@types/chai-as-promised@npm:^7.1.8": version: 7.1.8 resolution: "@types/chai-as-promised@npm:7.1.8" @@ -12535,6 +12546,13 @@ __metadata: languageName: node linkType: hard +"@types/long@npm:^3.0.0": + version: 3.0.32 + resolution: "@types/long@npm:3.0.32" + checksum: 10/cc5422875a085b49b74ffeb5c60a8681d30f700859a8931012b4a58c5c6005cdacb4d3ce3e5af7a7f579ee20d5c2e442a773a83b3a4f7a2d39795a7a8e9a962d + languageName: node + linkType: hard + "@types/mailparser@npm:^3.4.4": version: 3.4.4 resolution: "@types/mailparser@npm:3.4.4" From 70f57301d6d5eb41ce386baaddcaaaa59313c680 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 21 Oct 2024 10:40:24 -0600 Subject: [PATCH 12/12] chore: Replace some `cursor.count` calls with `countDocuments` or `estimatedDocumentCount` (#33671) --- .../app/statistics/server/lib/statistics.ts | 16 +++++++------- apps/meteor/package.json | 2 +- apps/meteor/server/models/raw/BaseRaw.ts | 6 +++++- apps/meteor/server/models/raw/NpsVote.ts | 13 ++++++++++++ apps/meteor/server/models/raw/Rooms.ts | 19 +++++++++++++++++ apps/meteor/server/models/raw/ServerEvents.ts | 16 +++++++------- apps/meteor/server/models/raw/TeamMember.ts | 8 +++++++ .../server/models/raw/VideoConference.ts | 17 +++++++++++---- apps/meteor/server/services/nps/service.ts | 6 +++--- apps/meteor/server/services/team/service.ts | 21 +++++++------------ .../model-typings/src/models/INpsVoteModel.ts | 2 ++ .../model-typings/src/models/IRoomsModel.ts | 4 ++++ .../src/models/ITeamMemberModel.ts | 3 +++ 13 files changed, 95 insertions(+), 38 deletions(-) diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 84b8f23f7790..9defbaa42bef 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -126,10 +126,10 @@ export const statistics = { // Room statistics statistics.totalRooms = await Rooms.col.countDocuments({}); - statistics.totalChannels = await Rooms.findByType('c').count(); - statistics.totalPrivateGroups = await Rooms.findByType('p').count(); - statistics.totalDirect = await Rooms.findByType('d').count(); - statistics.totalLivechat = await Rooms.findByType('l').count(); + statistics.totalChannels = await Rooms.countByType('c'); + statistics.totalPrivateGroups = await Rooms.countByType('p'); + statistics.totalDirect = await Rooms.countByType('d'); + statistics.totalLivechat = await Rooms.countByType('l'); statistics.totalDiscussions = await Rooms.countDiscussions(); statistics.totalThreads = await Messages.countThreads(); @@ -183,7 +183,7 @@ export const statistics = { // Number of triggers statsPms.push( - LivechatTrigger.col.count().then((count) => { + LivechatTrigger.estimatedDocumentCount().then((count) => { statistics.totalTriggers = count; }), ); @@ -205,13 +205,13 @@ export const statistics = { // Number of Email Inboxes statsPms.push( - EmailInbox.col.count().then((count) => { + EmailInbox.estimatedDocumentCount().then((count) => { statistics.emailInboxes = count; }), ); statsPms.push( - LivechatBusinessHours.col.count().then((count) => { + LivechatBusinessHours.estimatedDocumentCount().then((count) => { statistics.BusinessHours = { // Number of Business Hours total: count, @@ -556,7 +556,7 @@ export const statistics = { statistics.totalUserEmail2fa = await Users.countActiveUsersEmail2faEnable({ readPreference }); statistics.totalPinned = await Messages.findPinned({ readPreference }).count(); statistics.totalStarred = await Messages.findStarred({ readPreference }).count(); - statistics.totalLinkInvitation = await Invites.find().count(); + statistics.totalLinkInvitation = await Invites.estimatedDocumentCount(); statistics.totalLinkInvitationUses = await Invites.countUses(); statistics.totalEmailInvitation = settings.get('Invitation_Email_Count'); statistics.totalE2ERooms = await Rooms.findByE2E({ readPreference }).count(); diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 585fefde1795..703e604f9b03 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -15,7 +15,7 @@ "scripts": { "start": "meteor", "build:ci": "METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build --server-only --directory /tmp/dist", - "dev": "meteor --exclude-archs \"web.browser.legacy, web.cordova\"", + "dev": "NODE_OPTIONS=\"--trace-warnings\" meteor --exclude-archs \"web.browser.legacy, web.cordova\"", "dsv": "meteor npm run dev", "ha": "meteor npm run ha:start", "ms": "TRANSPORTER=${TRANSPORTER:-TCP} meteor npm run dev", diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts index 0a7fc56449b0..2376d84f8980 100644 --- a/apps/meteor/server/models/raw/BaseRaw.ts +++ b/apps/meteor/server/models/raw/BaseRaw.ts @@ -28,6 +28,7 @@ import type { DeleteResult, DeleteOptions, FindOneAndDeleteOptions, + CountDocumentsOptions, } from 'mongodb'; import { setUpdatedAt } from './setUpdatedAt'; @@ -497,7 +498,10 @@ export abstract class BaseRaw< return this.col.watch(pipeline); } - countDocuments(query: Filter): Promise { + countDocuments(query: Filter, options?: CountDocumentsOptions): Promise { + if (options) { + return this.col.countDocuments(query, options); + } return this.col.countDocuments(query); } diff --git a/apps/meteor/server/models/raw/NpsVote.ts b/apps/meteor/server/models/raw/NpsVote.ts index 508c6302c596..1559444fe631 100644 --- a/apps/meteor/server/models/raw/NpsVote.ts +++ b/apps/meteor/server/models/raw/NpsVote.ts @@ -97,4 +97,17 @@ export class NpsVoteRaw extends BaseRaw implements INpsVoteModel { }; return this.updateMany(query, update); } + + countByNpsId(npsId: string): Promise { + return this.countDocuments({ npsId }); + } + + countByNpsIdAndStatus(npsId: string, status: INpsVoteStatus): Promise { + const query = { + npsId, + status, + }; + + return this.countDocuments(query); + } } diff --git a/apps/meteor/server/models/raw/Rooms.ts b/apps/meteor/server/models/raw/Rooms.ts index 05afc102d164..7fc3be7479ed 100644 --- a/apps/meteor/server/models/raw/Rooms.ts +++ b/apps/meteor/server/models/raw/Rooms.ts @@ -224,6 +224,17 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { return this.find(query, options); } + countByTeamId(teamId: ITeam['_id']): Promise { + const query: Filter = { + teamId, + teamMain: { + $exists: false, + }, + }; + + return this.countDocuments(query); + } + findPaginatedByTeamIdContainingNameAndDefault( teamId: ITeam['_id'], name: IRoom['name'], @@ -702,6 +713,14 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { }); } + countRoomsInsideTeams(autoJoin = false): Promise { + return this.countDocuments({ + teamId: { $exists: true }, + teamMain: { $exists: false }, + ...(autoJoin && { teamDefault: true }), + }); + } + countByType(t: IRoom['t']): Promise { return this.col.countDocuments({ t }); } diff --git a/apps/meteor/server/models/raw/ServerEvents.ts b/apps/meteor/server/models/raw/ServerEvents.ts index ef6365c9609b..a36288e7604d 100644 --- a/apps/meteor/server/models/raw/ServerEvents.ts +++ b/apps/meteor/server/models/raw/ServerEvents.ts @@ -55,36 +55,36 @@ export class ServerEventsRaw extends BaseRaw implements IServerEve } async countFailedAttemptsByUsernameSince(username: string, since: Date): Promise { - return this.find({ + return this.countDocuments({ 'u.username': username, 't': ServerEventType.FAILED_LOGIN_ATTEMPT, 'ts': { $gte: since, }, - }).count(); + }); } countFailedAttemptsByIpSince(ip: string, since: Date): Promise { - return this.find({ + return this.countDocuments({ ip, t: ServerEventType.FAILED_LOGIN_ATTEMPT, ts: { $gte: since, }, - }).count(); + }); } countFailedAttemptsByIp(ip: string): Promise { - return this.find({ + return this.countDocuments({ ip, t: ServerEventType.FAILED_LOGIN_ATTEMPT, - }).count(); + }); } countFailedAttemptsByUsername(username: string): Promise { - return this.find({ + return this.countDocuments({ 'u.username': username, 't': ServerEventType.FAILED_LOGIN_ATTEMPT, - }).count(); + }); } } diff --git a/apps/meteor/server/models/raw/TeamMember.ts b/apps/meteor/server/models/raw/TeamMember.ts index 53c19b0c2fef..0a5b2a2755f4 100644 --- a/apps/meteor/server/models/raw/TeamMember.ts +++ b/apps/meteor/server/models/raw/TeamMember.ts @@ -72,6 +72,10 @@ export class TeamMemberRaw extends BaseRaw implements ITeamMemberMo return options ? this.col.find({ teamId }, options) : this.col.find({ teamId }, options); } + countByTeamId(teamId: string): Promise { + return this.countDocuments({ teamId }); + } + findByTeamIds(teamIds: Array): FindCursor; findByTeamIds(teamIds: Array, options: FindOptions): FindCursor; @@ -99,6 +103,10 @@ export class TeamMemberRaw extends BaseRaw implements ITeamMemberMo return options ? this.col.find({ teamId, roles: role }, options) : this.col.find({ teamId, roles: role }); } + countByTeamIdAndRole(teamId: string, role: IRole['_id']): Promise { + return this.countDocuments({ teamId, roles: role }); + } + findByUserIdAndTeamIds(userId: string, teamIds: Array, options: FindOptions = {}): FindCursor { const query = { userId, diff --git a/apps/meteor/server/models/raw/VideoConference.ts b/apps/meteor/server/models/raw/VideoConference.ts index 73080ac7b41e..4c324d938e2a 100644 --- a/apps/meteor/server/models/raw/VideoConference.ts +++ b/apps/meteor/server/models/raw/VideoConference.ts @@ -8,7 +8,16 @@ import type { } from '@rocket.chat/core-typings'; import { VideoConferenceStatus } from '@rocket.chat/core-typings'; import type { FindPaginated, InsertionModel, IVideoConferenceModel } from '@rocket.chat/model-typings'; -import type { FindCursor, UpdateOptions, UpdateFilter, UpdateResult, IndexDescription, Collection, Db, FindOptions } from 'mongodb'; +import type { + FindCursor, + UpdateOptions, + UpdateFilter, + UpdateResult, + IndexDescription, + Collection, + Db, + CountDocumentsOptions, +} from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -63,15 +72,15 @@ export class VideoConferenceRaw extends BaseRaw implements IVid public async countByTypeAndStatus( type: VideoConference['type'], status: VideoConferenceStatus, - options: FindOptions, + options: CountDocumentsOptions, ): Promise { - return this.find( + return this.countDocuments( { type, status, }, options, - ).count(); + ); } public async createDirect({ diff --git a/apps/meteor/server/services/nps/service.ts b/apps/meteor/server/services/nps/service.ts index be5b44582a5a..d1c5023fca89 100644 --- a/apps/meteor/server/services/nps/service.ts +++ b/apps/meteor/server/services/nps/service.ts @@ -60,14 +60,14 @@ export class NPSService extends ServiceClassInternal implements INPSService { return; } - const total = await NpsVote.findByNpsId(nps._id).count(); + const total = await NpsVote.countByNpsId(nps._id); const votesToSend = await NpsVote.findNotSentByNpsId(nps._id).toArray(); // if there is nothing to sent, check if something gone wrong if (votesToSend.length === 0) { // check if still has votes left to send - const totalSent = await NpsVote.findByNpsIdAndStatus(nps._id, INpsVoteStatus.SENT).count(); + const totalSent = await NpsVote.countByNpsIdAndStatus(nps._id, INpsVoteStatus.SENT); if (totalSent === total) { await Nps.updateStatusById(nps._id, NPSStatus.SENT); return; @@ -130,7 +130,7 @@ export class NPSService extends ServiceClassInternal implements INPSService { await NpsVote.updateVotesToSent(voteIds); } - const totalSent = await NpsVote.findByNpsIdAndStatus(nps._id, INpsVoteStatus.SENT).count(); + const totalSent = await NpsVote.countByNpsIdAndStatus(nps._id, INpsVoteStatus.SENT); if (totalSent < total) { // send more in five minutes setTimeout(() => NPS.sendResults(), 5 * 60 * 1000); diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index bdca222b246c..85da5cb68a17 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -251,12 +251,10 @@ export class TeamService extends ServiceClassInternal implements ITeamService { const results: ITeamInfo[] = []; for await (const record of records) { - const rooms = Rooms.findByTeamId(record._id); - const users = TeamMember.findByTeamId(record._id); results.push({ ...record, - rooms: await rooms.count(), - numberOfUsers: await users.count(), + rooms: await Rooms.countByTeamId(record._id), + numberOfUsers: await TeamMember.countByTeamId(record._id), }); } @@ -279,12 +277,10 @@ export class TeamService extends ServiceClassInternal implements ITeamService { const results: ITeamInfo[] = []; for await (const record of records) { - const rooms = Rooms.findByTeamId(record._id); - const users = TeamMember.findByTeamId(record._id); results.push({ ...record, - rooms: await rooms.count(), - numberOfUsers: await users.count(), + rooms: await Rooms.countByTeamId(record._id), + numberOfUsers: await TeamMember.countByTeamId(record._id), }); } @@ -797,8 +793,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService { if (existingMember) { if (existingMember.roles?.includes('owner')) { - const owners = TeamMember.findByTeamIdAndRole(team._id, 'owner'); - const totalOwners = await owners.count(); + const totalOwners = await TeamMember.countByTeamIdAndRole(team._id, 'owner'); if (totalOwners === 1) { throw new Error('last-owner-can-not-be-removed'); } @@ -1002,9 +997,9 @@ export class TeamService extends ServiceClassInternal implements ITeamService { async getStatistics(): Promise { return { - totalTeams: await Team.find({}).count(), - totalRoomsInsideTeams: await Rooms.findRoomsInsideTeams().count(), - totalDefaultRoomsInsideTeams: await Rooms.findRoomsInsideTeams(true).count(), + totalTeams: await Team.estimatedDocumentCount(), + totalRoomsInsideTeams: await Rooms.countRoomsInsideTeams(), + totalDefaultRoomsInsideTeams: await Rooms.countRoomsInsideTeams(true), }; } diff --git a/packages/model-typings/src/models/INpsVoteModel.ts b/packages/model-typings/src/models/INpsVoteModel.ts index a40bbef1b786..0b764299fb76 100644 --- a/packages/model-typings/src/models/INpsVoteModel.ts +++ b/packages/model-typings/src/models/INpsVoteModel.ts @@ -10,4 +10,6 @@ export interface INpsVoteModel extends IBaseModel { save(vote: Omit): Promise; updateVotesToSent(voteIds: string[]): Promise; updateOldSendingToNewByNpsId(npsId: string): Promise; + countByNpsId(npsId: string): Promise; + countByNpsIdAndStatus(npsId: string, status: INpsVoteStatus): Promise; } diff --git a/packages/model-typings/src/models/IRoomsModel.ts b/packages/model-typings/src/models/IRoomsModel.ts index 74d2fbe301e9..6419d021887e 100644 --- a/packages/model-typings/src/models/IRoomsModel.ts +++ b/packages/model-typings/src/models/IRoomsModel.ts @@ -49,6 +49,8 @@ export interface IRoomsModel extends IBaseModel { findByTeamId(teamId: ITeam['_id'], options?: FindOptions): FindCursor; + countByTeamId(teamId: ITeam['_id']): Promise; + findPaginatedByTeamIdContainingNameAndDefault( teamId: ITeam['_id'], name: IRoom['name'], @@ -139,6 +141,8 @@ export interface IRoomsModel extends IBaseModel { findRoomsInsideTeams(autoJoin?: boolean): FindCursor; + countRoomsInsideTeams(autoJoin?: boolean): Promise; + findOneDirectRoomContainingAllUserIDs(uid: IDirectMessageRoom['uids'], options?: FindOptions): Promise; countByType(t: IRoom['t']): Promise; diff --git a/packages/model-typings/src/models/ITeamMemberModel.ts b/packages/model-typings/src/models/ITeamMemberModel.ts index 46093c05458f..0a4264c9579a 100644 --- a/packages/model-typings/src/models/ITeamMemberModel.ts +++ b/packages/model-typings/src/models/ITeamMemberModel.ts @@ -38,6 +38,7 @@ export interface ITeamMemberModel extends IBaseModel { options?: undefined | FindOptions | FindOptions

, ): FindCursor

| FindCursor; + countByTeamId(teamId: string): Promise; findByTeamIds(teamIds: Array): FindCursor; findByTeamIds(teamIds: Array, options: FindOptions): FindCursor; @@ -61,6 +62,8 @@ export interface ITeamMemberModel extends IBaseModel { options?: undefined | FindOptions | FindOptions

, ): FindCursor

| FindCursor; + countByTeamIdAndRole(teamId: string, role: IRole['_id']): Promise; + findByUserIdAndTeamIds(userId: string, teamIds: Array, options?: FindOptions): FindCursor; findPaginatedMembersInfoByTeamId(