From 8e03a0c48641ad1b31c861c2342483fdad77974e Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 2 Oct 2023 20:50:31 -0300 Subject: [PATCH 1/7] chore: set license public key v3 with v2 (#30548) --- ee/packages/license/src/token.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/ee/packages/license/src/token.ts b/ee/packages/license/src/token.ts index 80ecc29b4a3f..2a9836a48303 100644 --- a/ee/packages/license/src/token.ts +++ b/ee/packages/license/src/token.ts @@ -4,10 +4,10 @@ import { verify, sign, getPairs } from '@rocket.chat/jwt'; import type { ILicenseV3 } from './definition/ILicenseV3'; -const PUBLIC_KEY_V2 = +const PUBLIC_LICENSE_KEY_V2 = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFxV1Nza2Q5LzZ6Ung4a3lQY2ljcwpiMzJ3Mnd4VnV3N3lCVDk2clEvOEQreU1lQ01POXdTU3BIYS85bkZ5d293RXRpZ3B0L3dyb1BOK1ZHU3didHdQCkZYQmVxRWxCbmRHRkFsODZlNStFbGlIOEt6L2hHbkNtSk5tWHB4RUsyUkUwM1g0SXhzWVg3RERCN010eC9pcXMKY2pCL091dlNCa2ppU2xlUzdibE5JVC9kQTdLNC9DSjNvaXUwMmJMNEV4Y2xDSGVwenFOTWVQM3dVWmdweE9uZgpOT3VkOElYWUs3M3pTY3VFOEUxNTdZd3B6Q0twVmFIWDdaSmY4UXVOc09PNVcvYUlqS2wzTDYyNjkrZUlPRXJHCndPTm1hSG56Zmc5RkxwSmh6Z3BPMzhhVm43NnZENUtLakJhaldza1krNGEyZ1NRbUtOZUZxYXFPb3p5RUZNMGUKY0ZXWlZWWjNMZWg0dkVNb1lWUHlJeng5Nng4ZjIveW1QbmhJdXZRdjV3TjRmeWVwYTdFWTVVQ2NwNzF6OGtmUAo0RmNVelBBMElEV3lNaWhYUi9HNlhnUVFaNEdiL3FCQmh2cnZpSkNGemZZRGNKZ0w3RmVnRllIUDNQR0wwN1FnCnZMZXZNSytpUVpQcnhyYnh5U3FkUE9rZ3VyS2pWclhUVXI0QTlUZ2lMeUlYNVVsSnEzRS9SVjdtZk9xWm5MVGEKU0NWWEhCaHVQbG5DR1pSMDFUb1RDZktoTUcxdTBDRm5MMisxNWhDOWZxT21XdjlRa2U0M3FsSjBQZ0YzVkovWAp1eC9tVHBuazlnbmJHOUpIK21mSDM5Um9GdlROaW5Zd1NNdll6dXRWT242OXNPemR3aERsYTkwbDNBQ2g0eENWCks3Sk9YK3VIa29OdTNnMmlWeGlaVU0wQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo='; -const PUBLIC_KEY_V3 = ''; +const PUBLIC_LICENSE_KEY_V3 = process.env.PUBLIC_LICENSE_KEY_V3 || PUBLIC_LICENSE_KEY_V2; let TEST_KEYS: [string, string] | undefined = undefined; @@ -19,7 +19,7 @@ export async function decrypt(encrypted: string): Promise { TEST_KEYS = TEST_KEYS ?? (await getPairs()); if (!TEST_KEYS) { - throw new Error('Missing LICENSE_PUBLIC_KEY_V3'); + throw new Error('Missing PUBLIC_LICENSE_KEY_V3'); } const [spki] = TEST_KEYS; @@ -32,12 +32,12 @@ export async function decrypt(encrypted: string): Promise { // handle V3 if (encrypted.startsWith('RCV3_')) { const jwt = encrypted.substring(5); - const [payload] = await verify(jwt, PUBLIC_KEY_V3); + const [payload] = await verify(jwt, PUBLIC_LICENSE_KEY_V3); return JSON.stringify(payload); } - const decrypted = crypto.publicDecrypt(Buffer.from(PUBLIC_KEY_V2, 'base64').toString('utf-8'), Buffer.from(encrypted, 'base64')); + const decrypted = crypto.publicDecrypt(Buffer.from(PUBLIC_LICENSE_KEY_V2, 'base64').toString('utf-8'), Buffer.from(encrypted, 'base64')); return decrypted.toString('utf-8'); } @@ -49,10 +49,6 @@ export async function encrypt(license: ILicenseV3): Promise { TEST_KEYS = TEST_KEYS ?? (await getPairs()); - if (!TEST_KEYS) { - throw new Error('Missing LICENSE_PUBLIC_KEY_V3'); - } - const [, pkcs8] = TEST_KEYS; return `RCV3_${await sign(license, pkcs8)}`; From b14e159d9b5e16839642c8575beef3540f453080 Mon Sep 17 00:00:00 2001 From: Anshul Singh <68077049+Rottenblasters@users.noreply.github.com> Date: Tue, 3 Oct 2023 05:36:41 +0530 Subject: [PATCH 2/7] fix: in forward search field, user cannot be found by name (Full Name) (#29663) --- .changeset/quiet-phones-reply.md | 5 +++++ .../UserAndRoomAutoCompleteMultiple.tsx | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/quiet-phones-reply.md diff --git a/.changeset/quiet-phones-reply.md b/.changeset/quiet-phones-reply.md new file mode 100644 index 000000000000..f2735e615491 --- /dev/null +++ b/.changeset/quiet-phones-reply.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Search users using full name too on share message modal diff --git a/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx b/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx index 96d11cf44cb7..f96c198865cc 100644 --- a/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx +++ b/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx @@ -18,7 +18,16 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndR const debouncedFilter = useDebouncedValue(filter, 1000); const rooms = useUserSubscriptions( - useMemo(() => ({ open: { $ne: false }, lowerCaseName: new RegExp(escapeRegExp(debouncedFilter), 'i') }), [debouncedFilter]), + useMemo( + () => ({ + open: { $ne: false }, + $or: [ + { lowerCaseFName: new RegExp(escapeRegExp(debouncedFilter), 'i') }, + { lowerCaseName: new RegExp(escapeRegExp(debouncedFilter), 'i') }, + ], + }), + [debouncedFilter], + ), ).filter((room) => { if (!user) { return; From d23156daaadefa8f57bafd82c391a33557b67c01 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 2 Oct 2023 21:47:06 -0300 Subject: [PATCH 3/7] chore: add the new endpoints to sync with cloud (#30546) --- .../server/functions/buildRegistrationData.ts | 44 ++-- .../server/functions/getWorkspaceLicense.ts | 8 +- .../supportedVersionsToken.ts | 13 +- .../syncWorkspace/announcementSync.ts | 116 +++++++++++ .../syncWorkspace/handleCommsSync.ts | 65 ++++++ .../server/functions/syncWorkspace/index.ts | 4 +- .../syncWorkspace/legacySyncWorkspace.ts | 182 +++++++++++++++++ .../functions/syncWorkspace/syncCloudData.ts | 189 ++---------------- ee/packages/license/package.json | 3 +- .../license/src/definition/ILicenseV3.ts | 4 - ee/packages/license/src/index.ts | 8 +- .../src/cloud/WorkspaceSyncPayload.ts | 38 +++- packages/core-typings/src/cloud/index.ts | 8 +- .../server-cloud-communication/package.json | 4 + .../server-cloud-communication/src/index.ts | 2 + yarn.lock | 1 + 16 files changed, 470 insertions(+), 219 deletions(-) create mode 100644 apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts create mode 100644 apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts create mode 100644 apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts index 10e0d7f7f7ee..2ad8ba29072a 100644 --- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts @@ -1,50 +1,52 @@ -import type { SettingValue } from '@rocket.chat/core-typings'; import { Statistics, Users } from '@rocket.chat/models'; import { settings } from '../../../settings/server'; import { statistics } from '../../../statistics/server'; +import { Info } from '../../../utils/rocketchat.info'; import { LICENSE_VERSION } from '../license'; export type WorkspaceRegistrationData = { uniqueId: string; - workspaceId: SettingValue; - address: SettingValue; + workspaceId: string; + address: string; contactName: string; contactEmail: T; seats: number; - allowMarketing: SettingValue; - accountName: SettingValue; + organizationType: string; industry: string; orgSize: string; country: string; language: string; - agreePrivacyTerms: SettingValue; - website: SettingValue; - siteName: SettingValue; + allowMarketing: string; + accountName: string; + agreePrivacyTerms: string; + website: string; + siteName: string; workspaceType: unknown; deploymentMethod: string; deploymentPlatform: string; - version: unknown; + version: string; licenseVersion: number; enterpriseReady: boolean; setupComplete: boolean; connectionDisable: boolean; - npsEnabled: SettingValue; + npsEnabled: string; + MAC: number; }; export async function buildWorkspaceRegistrationData(contactEmail: T): Promise> { const stats = (await Statistics.findLast()) || (await statistics.get()); - const address = settings.get('Site_Url'); - const siteName = settings.get('Site_Name'); - const workspaceId = settings.get('Cloud_Workspace_Id'); - const allowMarketing = settings.get('Allow_Marketing_Emails'); - const accountName = settings.get('Organization_Name'); - const website = settings.get('Website'); - const npsEnabled = settings.get('NPS_survey_enabled'); - const agreePrivacyTerms = settings.get('Cloud_Service_Agree_PrivacyTerms'); - const setupWizardState = settings.get('Show_Setup_Wizard'); + const address = settings.get('Site_Url'); + const siteName = settings.get('Site_Name'); + const workspaceId = settings.get('Cloud_Workspace_Id'); + const allowMarketing = settings.get('Allow_Marketing_Emails'); + const accountName = settings.get('Organization_Name'); + const website = settings.get('Website'); + const npsEnabled = settings.get('NPS_survey_enabled'); + const agreePrivacyTerms = settings.get('Cloud_Service_Agree_PrivacyTerms'); + const setupWizardState = settings.get('Show_Setup_Wizard'); const firstUser = await Users.getOldest({ projection: { name: 1, emails: 1 } }); const contactName = firstUser?.name || ''; @@ -72,11 +74,13 @@ export async function buildWorkspaceRegistrationData { const currentLicense = await Settings.findOne('Cloud_Workspace_License'); + // it should never happen, since even if the license is not found, it will return an empty settings + if (!currentLicense?._updatedAt) { + throw new CloudWorkspaceLicenseError('Failed to retrieve current license'); + } const fromCurrentLicense = async () => { const license = currentLicense?.value as string | undefined; @@ -67,10 +71,6 @@ export async function getWorkspaceLicense(): Promise<{ updated: boolean; license return fromCurrentLicense(); } - if (!currentLicense?._updatedAt) { - throw new CloudWorkspaceLicenseError('Failed to retrieve current license'); - } - const payload = await fetchCloudWorkspaceLicensePayload({ token }); if (Date.parse(payload.updatedAt) <= currentLicense._updatedAt.getTime()) { diff --git a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts index 3d79ed436e51..577abd4383d0 100644 --- a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts +++ b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts @@ -1,7 +1,7 @@ import type { SettingValue } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { Settings } from '@rocket.chat/models'; -import type { SupportedVersions } from '@rocket.chat/server-cloud-communication'; +import type { SignedSupportedVersions, SupportedVersions } from '@rocket.chat/server-cloud-communication'; import type { Response } from '@rocket.chat/server-fetch'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; @@ -10,6 +10,12 @@ import { settings } from '../../../../settings/server'; import { generateWorkspaceBearerHttpHeader } from '../getWorkspaceAccessToken'; import { supportedVersionsChooseLatest } from './supportedVersionsChooseLatest'; +declare module '@rocket.chat/license' { + interface ILicenseV3 { + supportedVersions?: SignedSupportedVersions; + } +} + /** HELPERS */ export const wrapPromise = ( @@ -115,9 +121,10 @@ const getSupportedVersionsToken = async () => { * return the token */ - const [versionsFromLicense, response] = await Promise.all([License.supportedVersions(), getSupportedVersionsFromCloud()]); + const [versionsFromLicense, response] = await Promise.all([License.getLicense(), getSupportedVersionsFromCloud()]); - return (await supportedVersionsChooseLatest(versionsFromLicense, (response.success && response.result) || undefined))?.signed; + return (await supportedVersionsChooseLatest(versionsFromLicense?.supportedVersions, (response.success && response.result) || undefined)) + ?.signed; }; export const getCachedSupportedVersionsToken = cacheValueInSettings('Cloud_Workspace_Supported_Versions_Token', getSupportedVersionsToken); diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts new file mode 100644 index 000000000000..26d98b4a7574 --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts @@ -0,0 +1,116 @@ +import { type Cloud, type Serialized } from '@rocket.chat/core-typings'; +import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { v, compile } from 'suretype'; + +import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError'; +import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; +import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; +import { SystemLogger } from '../../../../../server/lib/logger/system'; +import { settings } from '../../../../settings/server'; +import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; +import { getWorkspaceAccessToken } from '../getWorkspaceAccessToken'; +import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus'; +import { handleAnnouncementsOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync'; +import { legacySyncWorkspace } from './legacySyncWorkspace'; + +const workspaceCommPayloadSchema = v.object({ + workspaceId: v.string().required(), + publicKey: v.string(), + nps: v.object({ + id: v.string().required(), + startAt: v.string().format('date-time').required(), + expireAt: v.string().format('date-time').required(), + }), + announcements: v.object({ + create: v.array( + v.object({ + _id: v.string().required(), + _updatedAt: v.string().format('date-time').required(), + selector: v.object({ + roles: v.array(v.string()), + }), + platform: v.array(v.string().enum('web', 'mobile')).required(), + expireAt: v.string().format('date-time').required(), + startAt: v.string().format('date-time').required(), + createdBy: v.string().enum('cloud', 'system').required(), + createdAt: v.string().format('date-time').required(), + dictionary: v.object({}).additional(v.object({}).additional(v.string())), + view: v.any(), + surface: v.string().enum('banner', 'modal').required(), + }), + ), + delete: v.array(v.string()), + }), +}); + +const assertWorkspaceCommPayload = compile(workspaceCommPayloadSchema); + +const fetchCloudAnnouncementsSync = async ({ + token, + data, +}: { + token: string; + data: Cloud.WorkspaceSyncRequestPayload; +}): Promise> => { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/v3/comms/workspace`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + body: data, + }); + + if (!response.ok) { + try { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } catch (error) { + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); + } + } + + const payload = await response.json(); + + assertWorkspaceCommPayload(payload); + return payload; +}; + +export async function announcementSync() { + try { + const { workspaceRegistered } = await retrieveRegistrationStatus(); + if (!workspaceRegistered) { + throw new CloudWorkspaceRegistrationError('Workspace is not registered'); + } + + const token = await getWorkspaceAccessToken(true); + if (!token) { + throw new CloudWorkspaceAccessError('Workspace does not have a valid access token'); + } + + const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); + + const { nps, announcements } = await fetchCloudAnnouncementsSync({ + token, + data: workspaceRegistrationData, + }); + + if (nps) { + await handleNpsOnWorkspaceSync(nps); + } + + if (announcements) { + await handleAnnouncementsOnWorkspaceSync(announcements); + } + + return true; + } catch (err) { + SystemLogger.error({ + msg: 'Failed to sync with Rocket.Chat Cloud', + url: '/sync', + err, + }); + } + + await legacySyncWorkspace(); +} diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts new file mode 100644 index 000000000000..c8b07f8826cf --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts @@ -0,0 +1,65 @@ +import { NPS, Banner } from '@rocket.chat/core-services'; +import { type Cloud, type Serialized } from '@rocket.chat/core-typings'; +import { CloudAnnouncements } from '@rocket.chat/models'; + +import { getAndCreateNpsSurvey } from '../../../../../server/services/nps/getAndCreateNpsSurvey'; + +export const handleNpsOnWorkspaceSync = async (nps: Exclude['nps'], undefined>) => { + const { id: npsId, expireAt } = nps; + + const startAt = new Date(nps.startAt); + + await NPS.create({ + npsId, + startAt, + expireAt: new Date(expireAt), + createdBy: { + _id: 'rocket.cat', + username: 'rocket.cat', + }, + }); + + const now = new Date(); + + if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) { + await getAndCreateNpsSurvey(npsId); + } +}; + +export const handleBannerOnWorkspaceSync = async (banners: Exclude['banners'], undefined>) => { + for await (const banner of banners) { + const { createdAt, expireAt, startAt, inactivedAt, _updatedAt, ...rest } = banner; + + await Banner.create({ + ...rest, + createdAt: new Date(createdAt), + expireAt: new Date(expireAt), + startAt: new Date(startAt), + ...(inactivedAt && { inactivedAt: new Date(inactivedAt) }), + }); + } +}; + +const deserializeAnnouncement = (announcement: Serialized): Cloud.Announcement => ({ + ...announcement, + _updatedAt: new Date(announcement._updatedAt), + expireAt: new Date(announcement.expireAt), + startAt: new Date(announcement.startAt), + createdAt: new Date(announcement.createdAt), +}); + +export const handleAnnouncementsOnWorkspaceSync = async ( + announcements: Exclude['announcements'], undefined>, +) => { + const { create, delete: deleteIds } = announcements; + + if (deleteIds) { + await CloudAnnouncements.deleteMany({ _id: { $in: deleteIds } }); + } + + for await (const announcement of create.map(deserializeAnnouncement)) { + const { _id, ...rest } = announcement; + + await CloudAnnouncements.updateOne({ _id }, { $set: rest }, { upsert: true }); + } +}; diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts index 48d5afa9dbc5..3173e652afe5 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts @@ -1,12 +1,12 @@ import { CloudWorkspaceAccessTokenError } from '../getWorkspaceAccessToken'; -import { getWorkspaceLicense } from '../getWorkspaceLicense'; import { getCachedSupportedVersionsToken } from '../supportedVersionsToken/supportedVersionsToken'; +import { announcementSync } from './announcementSync'; import { syncCloudData } from './syncCloudData'; export async function syncWorkspace() { try { await syncCloudData(); - await getWorkspaceLicense(); + await announcementSync(); } catch (error) { if (error instanceof CloudWorkspaceAccessTokenError) { // TODO: Remove License if there is no access token diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts new file mode 100644 index 000000000000..d5f86fad8409 --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts @@ -0,0 +1,182 @@ +import { type Cloud, type Serialized } from '@rocket.chat/core-typings'; +import { Settings } from '@rocket.chat/models'; +import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { v, compile } from 'suretype'; + +import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError'; +import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; +import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; +import { SystemLogger } from '../../../../../server/lib/logger/system'; +import { settings } from '../../../../settings/server'; +import type { WorkspaceRegistrationData } from '../buildRegistrationData'; +import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; +import { getWorkspaceAccessToken } from '../getWorkspaceAccessToken'; +import { getWorkspaceLicense } from '../getWorkspaceLicense'; +import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus'; +import { handleBannerOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync'; + +const workspaceClientPayloadSchema = v.object({ + workspaceId: v.string().required(), + publicKey: v.string(), + trial: v.object({ + trialing: v.boolean().required(), + trialID: v.string().required(), + endDate: v.string().format('date-time').required(), + marketing: v + .object({ + utmContent: v.string().required(), + utmMedium: v.string().required(), + utmSource: v.string().required(), + utmCampaign: v.string().required(), + }) + .required(), + DowngradesToPlan: v + .object({ + id: v.string().required(), + }) + .required(), + trialRequested: v.boolean().required(), + }), + nps: v.object({ + id: v.string().required(), + startAt: v.string().format('date-time').required(), + expireAt: v.string().format('date-time').required(), + }), + banners: v.array( + v.object({ + _id: v.string().required(), + _updatedAt: v.string().format('date-time').required(), + platform: v.array(v.string()).required(), + expireAt: v.string().format('date-time').required(), + startAt: v.string().format('date-time').required(), + roles: v.array(v.string()), + createdBy: v.object({ + _id: v.string().required(), + username: v.string(), + }), + createdAt: v.string().format('date-time').required(), + view: v.any(), + active: v.boolean(), + inactivedAt: v.string().format('date-time'), + snapshot: v.string(), + }), + ), + announcements: v.object({ + create: v.array( + v.object({ + _id: v.string().required(), + _updatedAt: v.string().format('date-time').required(), + selector: v.object({ + roles: v.array(v.string()), + }), + platform: v.array(v.string().enum('web', 'mobile')).required(), + expireAt: v.string().format('date-time').required(), + startAt: v.string().format('date-time').required(), + createdBy: v.string().enum('cloud', 'system').required(), + createdAt: v.string().format('date-time').required(), + dictionary: v.object({}).additional(v.object({}).additional(v.string())), + view: v.any(), + surface: v.string().enum('banner', 'modal').required(), + }), + ), + delete: v.array(v.string()), + }), +}); + +const assertWorkspaceClientPayload = compile(workspaceClientPayloadSchema); + +/** @deprecated */ +const fetchWorkspaceClientPayload = async ({ + token, + workspaceRegistrationData, +}: { + token: string; + workspaceRegistrationData: WorkspaceRegistrationData; +}): Promise | undefined> => { + const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); + const response = await fetch(`${workspaceRegistrationClientUri}/client`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + body: workspaceRegistrationData, + }); + + if (!response.ok) { + try { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } catch (error) { + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); + } + } + + const payload = await response.json(); + + if (!payload) { + return undefined; + } + + if (!assertWorkspaceClientPayload(payload)) { + throw new CloudWorkspaceConnectionError('Invalid response from Rocket.Chat Cloud'); + } + + return payload; +}; + +/** @deprecated */ +const consumeWorkspaceSyncPayload = async (result: Serialized) => { + if (result.publicKey) { + await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey); + } + + if (result.trial?.trialID) { + await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); + } + + // add banners + if (result.banners) { + await handleBannerOnWorkspaceSync(result.banners); + } + + if (result.nps) { + await handleNpsOnWorkspaceSync(result.nps); + } +}; + +/** @deprecated */ +export async function legacySyncWorkspace() { + try { + const { workspaceRegistered } = await retrieveRegistrationStatus(); + if (!workspaceRegistered) { + throw new CloudWorkspaceRegistrationError('Workspace is not registered'); + } + + const token = await getWorkspaceAccessToken(true); + if (!token) { + throw new CloudWorkspaceAccessError('Workspace does not have a valid access token'); + } + + const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); + + const payload = await fetchWorkspaceClientPayload({ token, workspaceRegistrationData }); + + if (!payload) { + return true; + } + + await consumeWorkspaceSyncPayload(payload); + + return true; + } catch (err) { + SystemLogger.error({ + msg: 'Failed to sync with Rocket.Chat Cloud', + url: '/client', + err, + }); + + return false; + } finally { + await getWorkspaceLicense(); + } +} diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts index df63dda6d563..5f529a4892ec 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts @@ -1,105 +1,40 @@ -import { NPS, Banner } from '@rocket.chat/core-services'; -import { type Cloud, type Serialized } from '@rocket.chat/core-typings'; -import { CloudAnnouncements, Settings } from '@rocket.chat/models'; +import type { Cloud, Serialized } from '@rocket.chat/core-typings'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { v, compile } from 'suretype'; +import { callbacks } from '../../../../../lib/callbacks'; import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError'; import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../../server/lib/logger/system'; -import { getAndCreateNpsSurvey } from '../../../../../server/services/nps/getAndCreateNpsSurvey'; import { settings } from '../../../../settings/server'; -import type { WorkspaceRegistrationData } from '../buildRegistrationData'; import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; import { getWorkspaceAccessToken } from '../getWorkspaceAccessToken'; -import { getWorkspaceLicense } from '../getWorkspaceLicense'; import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus'; +import { legacySyncWorkspace } from './legacySyncWorkspace'; const workspaceSyncPayloadSchema = v.object({ workspaceId: v.string().required(), publicKey: v.string(), - trial: v.object({ - trialing: v.boolean().required(), - trialID: v.string().required(), - endDate: v.string().format('date-time').required(), - marketing: v - .object({ - utmContent: v.string().required(), - utmMedium: v.string().required(), - utmSource: v.string().required(), - utmCampaign: v.string().required(), - }) - .required(), - DowngradesToPlan: v - .object({ - id: v.string().required(), - }) - .required(), - trialRequested: v.boolean().required(), - }), - nps: v.object({ - id: v.string().required(), - startAt: v.string().format('date-time').required(), - expireAt: v.string().format('date-time').required(), - }), - banners: v.array( - v.object({ - _id: v.string().required(), - _updatedAt: v.string().format('date-time').required(), - platform: v.array(v.string()).required(), - expireAt: v.string().format('date-time').required(), - startAt: v.string().format('date-time').required(), - roles: v.array(v.string()), - createdBy: v.object({ - _id: v.string().required(), - username: v.string(), - }), - createdAt: v.string().format('date-time').required(), - view: v.any(), - active: v.boolean(), - inactivedAt: v.string().format('date-time'), - snapshot: v.string(), - }), - ), - announcements: v.object({ - create: v.array( - v.object({ - _id: v.string().required(), - _updatedAt: v.string().format('date-time').required(), - selector: v.object({ - roles: v.array(v.string()), - }), - platform: v.array(v.string().enum('web', 'mobile')).required(), - expireAt: v.string().format('date-time').required(), - startAt: v.string().format('date-time').required(), - createdBy: v.string().enum('cloud', 'system').required(), - createdAt: v.string().format('date-time').required(), - dictionary: v.object({}).additional(v.object({}).additional(v.string())), - view: v.any(), - surface: v.string().enum('banner', 'modal').required(), - }), - ), - delete: v.array(v.string()), - }), + license: v.string().required(), }); const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema); const fetchWorkspaceSyncPayload = async ({ token, - workspaceRegistrationData, + data, }: { token: string; - workspaceRegistrationData: WorkspaceRegistrationData; -}): Promise | undefined> => { + data: Cloud.WorkspaceSyncRequestPayload; +}): Promise> => { const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); - const response = await fetch(`${workspaceRegistrationClientUri}/client`, { + const response = await fetch(`${workspaceRegistrationClientUri}/sync`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, - body: workspaceRegistrationData, + body: data, }); if (!response.ok) { @@ -113,98 +48,11 @@ const fetchWorkspaceSyncPayload = async ({ const payload = await response.json(); - if (!payload) { - return undefined; - } - assertWorkspaceSyncPayload(payload); return payload; }; -const handleNpsOnWorkspaceSync = async (nps: Exclude['nps'], undefined>) => { - const { id: npsId, expireAt } = nps; - - const startAt = new Date(nps.startAt); - - await NPS.create({ - npsId, - startAt, - expireAt: new Date(expireAt), - createdBy: { - _id: 'rocket.cat', - username: 'rocket.cat', - }, - }); - - const now = new Date(); - - if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) { - await getAndCreateNpsSurvey(npsId); - } -}; - -const handleBannerOnWorkspaceSync = async (banners: Exclude['banners'], undefined>) => { - for await (const banner of banners) { - const { createdAt, expireAt, startAt, inactivedAt, _updatedAt, ...rest } = banner; - - await Banner.create({ - ...rest, - createdAt: new Date(createdAt), - expireAt: new Date(expireAt), - startAt: new Date(startAt), - ...(inactivedAt && { inactivedAt: new Date(inactivedAt) }), - }); - } -}; - -const deserializeAnnouncement = (announcement: Serialized): Cloud.Announcement => ({ - ...announcement, - _updatedAt: new Date(announcement._updatedAt), - expireAt: new Date(announcement.expireAt), - startAt: new Date(announcement.startAt), - createdAt: new Date(announcement.createdAt), -}); - -const handleAnnouncementsOnWorkspaceSync = async ( - announcements: Exclude['announcements'], undefined>, -) => { - const { create, delete: deleteIds } = announcements; - - if (deleteIds) { - await CloudAnnouncements.deleteMany({ _id: { $in: deleteIds } }); - } - - for await (const announcement of create.map(deserializeAnnouncement)) { - const { _id, ...rest } = announcement; - - await CloudAnnouncements.updateOne({ _id }, { $set: rest }, { upsert: true }); - } -}; - -const consumeWorkspaceSyncPayload = async (result: Serialized) => { - if (result.publicKey) { - await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey); - } - - if (result.trial?.trialID) { - await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); - } - - if (result.nps) { - await handleNpsOnWorkspaceSync(result.nps); - } - - // add banners - if (result.banners) { - await handleBannerOnWorkspaceSync(result.banners); - } - - if (result.announcements) { - await handleAnnouncementsOnWorkspaceSync(result.announcements); - } -}; - export async function syncCloudData() { try { const { workspaceRegistered } = await retrieveRegistrationStatus(); @@ -219,24 +67,21 @@ export async function syncCloudData() { const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); - const payload = await fetchWorkspaceSyncPayload({ token, workspaceRegistrationData }); - - if (!payload) { - return true; - } + const { license } = await fetchWorkspaceSyncPayload({ + token, + data: workspaceRegistrationData, + }); - await consumeWorkspaceSyncPayload(payload); + await callbacks.run('workspaceLicenseChanged', license); return true; } catch (err) { SystemLogger.error({ msg: 'Failed to sync with Rocket.Chat Cloud', - url: '/client', + url: '/sync', err, }); - - return false; - } finally { - await getWorkspaceLicense(); } + + await legacySyncWorkspace(); } diff --git a/ee/packages/license/package.json b/ee/packages/license/package.json index 24ecdc30bc49..f6a1e7a2b7d5 100644 --- a/ee/packages/license/package.json +++ b/ee/packages/license/package.json @@ -42,7 +42,6 @@ "dependencies": { "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/jwt": "workspace:^", - "@rocket.chat/logger": "workspace:^", - "@rocket.chat/server-cloud-communication": "workspace:^" + "@rocket.chat/logger": "workspace:^" } } diff --git a/ee/packages/license/src/definition/ILicenseV3.ts b/ee/packages/license/src/definition/ILicenseV3.ts index e2a8bd424bb2..d3a2d7f572a3 100644 --- a/ee/packages/license/src/definition/ILicenseV3.ts +++ b/ee/packages/license/src/definition/ILicenseV3.ts @@ -1,5 +1,3 @@ -import type { SignedSupportedVersions } from '@rocket.chat/server-cloud-communication'; - import type { ILicenseTag } from './ILicenseTag'; import type { LicenseLimit } from './LicenseLimit'; import type { LicenseModule } from './LicenseModule'; @@ -61,8 +59,6 @@ export interface ILicenseV3 { monthlyActiveContacts?: LicenseLimit[]; }; cloudMeta?: Record; - - supportedVersions?: SignedSupportedVersions; } export type LicenseLimitKind = keyof ILicenseV3['limits']; diff --git a/ee/packages/license/src/index.ts b/ee/packages/license/src/index.ts index 11cf3bbbe4c5..9dbd94db53ed 100644 --- a/ee/packages/license/src/index.ts +++ b/ee/packages/license/src/index.ts @@ -1,4 +1,4 @@ -import type { ILicenseV3, LicenseLimitKind } from './definition/ILicenseV3'; +import type { LicenseLimitKind } from './definition/ILicenseV3'; import type { LimitContext } from './definition/LimitContext'; import { getAppsConfig, getMaxActiveUsers, getUnmodifiedLicenseAndModules } from './deprecated'; import { onLicense } from './events/deprecated'; @@ -45,8 +45,6 @@ interface License { onInvalidateLicense: typeof onInvalidateLicense; onLimitReached: typeof onLimitReached; - supportedVersions(): ILicenseV3['supportedVersions']; - // Deprecated: onLicense: typeof onLicense; // Deprecated: @@ -58,10 +56,6 @@ interface License { } export class LicenseImp extends LicenseManager implements License { - supportedVersions() { - return this.getLicense()?.supportedVersions; - } - validateFormat = validateFormat; hasModule = hasModule; diff --git a/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts b/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts index 964cb42571b2..fb95cfa4553c 100644 --- a/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts +++ b/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts @@ -7,10 +7,6 @@ import type { NpsSurveyAnnouncement } from './NpsSurveyAnnouncement'; export interface WorkspaceSyncPayload { workspaceId: string; publicKey?: string; - announcements?: { - create: Announcement[]; - delete: Announcement['_id'][]; - }; trial?: { trialing: boolean; trialID: string; @@ -31,3 +27,37 @@ export interface WorkspaceSyncPayload { /** @deprecated */ banners?: IBanner[]; } + +export interface WorkspaceSyncRequestPayload { + uniqueId: string; + workspaceId: string; + seats: number; + MAC: number; // Need to align on the property + address: string; + siteName: string; + deploymentMethod: string; + deploymentPlatform: string; + version: string; + licenseVersion: number; + connectionDisable: boolean; +} + +export interface WorkspaceSyncResponse { + workspaceId: string; + publicKey: string; + license: unknown; +} + +export interface WorkspaceCommsRequestPayload { + npsEnabled: boolean; + deploymentMethod: string; + deploymentPlatform: string; + version: string; +} +export interface WorkspaceCommsResponsePayload { + nps?: NpsSurveyAnnouncement | null; // Potentially consolidate into announcements + announcements?: { + create: Announcement[]; + delete: Announcement['_id'][]; + }; +} diff --git a/packages/core-typings/src/cloud/index.ts b/packages/core-typings/src/cloud/index.ts index b9c044b054e3..da0565a215ed 100644 --- a/packages/core-typings/src/cloud/index.ts +++ b/packages/core-typings/src/cloud/index.ts @@ -1,4 +1,10 @@ export { Announcement } from './Announcement'; export { NpsSurveyAnnouncement } from './NpsSurveyAnnouncement'; export { WorkspaceLicensePayload } from './WorkspaceLicensePayload'; -export { WorkspaceSyncPayload } from './WorkspaceSyncPayload'; +export { + WorkspaceSyncPayload, + WorkspaceSyncRequestPayload, + WorkspaceSyncResponse, + WorkspaceCommsRequestPayload, + WorkspaceCommsResponsePayload, +} from './WorkspaceSyncPayload'; diff --git a/packages/server-cloud-communication/package.json b/packages/server-cloud-communication/package.json index 9b091bbc464f..52a3ff801dac 100644 --- a/packages/server-cloud-communication/package.json +++ b/packages/server-cloud-communication/package.json @@ -3,12 +3,16 @@ "version": "0.0.1", "private": true, "devDependencies": { + "@rocket.chat/license": "workspace:^", "@types/jest": "~29.5.3", "eslint": "~8.45.0", "jest": "~29.6.1", "ts-jest": "~29.0.5", "typescript": "~5.1.6" }, + "volta": { + "extends": "../../package.json" + }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", diff --git a/packages/server-cloud-communication/src/index.ts b/packages/server-cloud-communication/src/index.ts index a18306b926eb..382400b0c72c 100644 --- a/packages/server-cloud-communication/src/index.ts +++ b/packages/server-cloud-communication/src/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + import type { SupportedVersions, SignedSupportedVersions } from './definitions'; export { SupportedVersions, SignedSupportedVersions }; diff --git a/yarn.lock b/yarn.lock index 265f9c7ab0d3..b915bb0f2e2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9426,6 +9426,7 @@ __metadata: version: 0.0.0-use.local resolution: "@rocket.chat/server-cloud-communication@workspace:packages/server-cloud-communication" dependencies: + "@rocket.chat/license": "workspace:^" "@types/jest": ~29.5.3 eslint: ~8.45.0 jest: ~29.6.1 From 83c7708832f40c35418a3c854fbeb1a9197da52b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 3 Oct 2023 00:55:23 -0300 Subject: [PATCH 4/7] chore: do not focus messagebox on mobile devices (#30553) --- .../client/messageBox/createComposerAPI.ts | 12 ++++++--- .../room/composer/messageBox/MessageBox.tsx | 2 +- .../hooks/useMessageBoxAutoFocus.ts | 25 ++++++++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts index 4609797e6bd2..a926f8540d27 100644 --- a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts +++ b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts @@ -48,13 +48,15 @@ export const createComposerAPI = (input: HTMLTextAreaElement, storageID: string) text: string, { selection, + skipFocus, }: { selection?: | { readonly start?: number; readonly end?: number } | ((previous: { readonly start: number; readonly end: number }) => { readonly start?: number; readonly end?: number }); + skipFocus?: boolean; } = {}, ): void => { - focus(); + !skipFocus && focus(); const { selectionStart, selectionEnd } = input; const textAreaTxt = input.value; @@ -66,7 +68,7 @@ export const createComposerAPI = (input: HTMLTextAreaElement, storageID: string) if (selection) { if (!document.execCommand?.('insertText', false, text)) { input.value = textAreaTxt.substring(0, selectionStart) + text + textAreaTxt.substring(selectionStart); - focus(); + !skipFocus && focus(); } input.setSelectionRange(selection.start ?? 0, selection.end ?? text.length); } @@ -78,7 +80,7 @@ export const createComposerAPI = (input: HTMLTextAreaElement, storageID: string) triggerEvent(input, 'input'); triggerEvent(input, 'change'); - focus(); + !skipFocus && focus(); }; const insertText = (text: string): void => { @@ -260,7 +262,9 @@ export const createComposerAPI = (input: HTMLTextAreaElement, storageID: string) const insertNewLine = (): void => insertText('\n'); - setText(Meteor._localStorage.getItem(storageID) ?? ''); + setText(Meteor._localStorage.getItem(storageID) ?? '', { + skipFocus: true, + }); // Gets the text that is connected to the cursor and replaces it with the given text const replaceText = (text: string, selection: { readonly start: number; readonly end: number }): void => { diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index de9a96dc43b4..da598c00be11 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -141,7 +141,7 @@ const MessageBox = ({ [chat, storageID], ); - const autofocusRef = useMessageBoxAutoFocus(); + const autofocusRef = useMessageBoxAutoFocus(!isMobile); const useEmojis = useUserPreference('useEmojis'); diff --git a/apps/meteor/client/views/room/composer/messageBox/hooks/useMessageBoxAutoFocus.ts b/apps/meteor/client/views/room/composer/messageBox/hooks/useMessageBoxAutoFocus.ts index 5ea6db79a869..b8efd9391f87 100644 --- a/apps/meteor/client/views/room/composer/messageBox/hooks/useMessageBoxAutoFocus.ts +++ b/apps/meteor/client/views/room/composer/messageBox/hooks/useMessageBoxAutoFocus.ts @@ -1,13 +1,13 @@ import type { Ref } from 'react'; -import { useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; /** * if the user is types outside the message box and its not actually typing in any input field * then the message box should be focused * @returns callbackRef to bind the logic to the message box */ -export const useMessageBoxAutoFocus = (): Ref => { - const ref = useRef(null); +export const useMessageBoxAutoFocus = (enabled: boolean): Ref => { + const ref = useRef(); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -43,5 +43,22 @@ export const useMessageBoxAutoFocus = (): Ref => { }; }, []); - return ref; + return useCallback( + (node: HTMLElement | null) => { + if (!node) { + return; + } + + ref.current = node; + + if (!enabled) { + return; + } + + if (ref.current) { + ref.current.focus(); + } + }, + [enabled, ref], + ); }; From c0ef13a0bfd254dc5837303c16f9fd655ba69736 Mon Sep 17 00:00:00 2001 From: Heitor Tanoue <68477006+heitortanoue@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:44:12 -0300 Subject: [PATCH 5/7] feat: push notification statistics (#30269) Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- .changeset/nice-chairs-add.md | 13 +++++++++++++ apps/meteor/app/statistics/server/lib/statistics.ts | 9 +++++++++ .../views/admin/info/DeploymentCard.stories.tsx | 1 + .../views/admin/info/InformationPage.stories.tsx | 1 + .../client/views/admin/info/UsageCard.stories.tsx | 1 + packages/core-typings/src/IStats.ts | 1 + 6 files changed, 26 insertions(+) create mode 100644 .changeset/nice-chairs-add.md diff --git a/.changeset/nice-chairs-add.md b/.changeset/nice-chairs-add.md new file mode 100644 index 000000000000..dfc9d763e1c0 --- /dev/null +++ b/.changeset/nice-chairs-add.md @@ -0,0 +1,13 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +--- + +Added `push` statistic, containing three bits. Each bit represents a boolean: +``` +1 1 1 +| | | +| | +- push enabled = 0b1 = 1 +| +--- push gateway enabled = 0b10 = 2 ++----- push gateway changed = 0b100 = 4 +``` diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index b6b983d92fce..89b068c11341 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -517,6 +517,15 @@ export const statistics = { statistics.totalWebRTCCalls = settings.get('WebRTC_Calls_Count'); statistics.uncaughtExceptionsCount = settings.get('Uncaught_Exceptions_Count'); + const defaultGateway = (await Settings.findOneById('Push_gateway', { projection: { packageValue: 1 } }))?.packageValue; + + // one bit for each of the following: + const pushEnabled = settings.get('Push_enable') ? 1 : 0; + const pushGatewayEnabled = settings.get('Push_enable_gateway') ? 2 : 0; + const pushGatewayChanged = settings.get('Push_gateway') !== defaultGateway ? 4 : 0; + + statistics.push = pushEnabled | pushGatewayEnabled | pushGatewayChanged; + const defaultHomeTitle = (await Settings.findOneById('Layout_Home_Title'))?.packageValue; statistics.homeTitleChanged = settings.get('Layout_Home_Title') !== defaultHomeTitle; diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx index ebb92b040c83..98aa3a7073ff 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx @@ -265,6 +265,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx index 222f31f88334..a6ef0c8e9289 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx @@ -295,6 +295,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx index 14a6cac8633d..da49ee88fa6b 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx @@ -243,6 +243,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index cd8aeb9f1762..6bbc2da81b74 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -211,6 +211,7 @@ export interface IStats { totalCustomRoles: number; totalWebRTCCalls: number; uncaughtExceptionsCount: number; + push: number; matrixFederation: { enabled: boolean; }; From 1065cd8870cc9df1d3824b91b8760bd4c12d3ec5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 3 Oct 2023 14:28:14 -0300 Subject: [PATCH 6/7] regression: fix initializing startup order (#30555) --- apps/meteor/ee/server/index.ts | 2 -- apps/meteor/server/main.ts | 3 ++- ee/packages/presence/package.json | 3 +++ packages/core-services/package.json | 3 +++ packages/core-services/src/lib/Api.ts | 6 +++++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 9b56239ad046..f5b385c9a805 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -1,5 +1,3 @@ -import './startup'; - import '../app/license/server/index'; import '../app/api-enterprise/server/index'; import '../app/authorization/server/index'; diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index 5579261911f5..09edca701540 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -9,9 +9,10 @@ import './importPackages'; import '../imports/startup/server'; import '../app/lib/server/startup'; +import '../ee/server/startup'; +import './startup'; import '../ee/server'; import './lib/pushConfig'; -import './startup'; import './configuration/accounts_meld'; import './configuration/ldap'; import './methods/OEmbedCacheCleanup'; diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json index 9011dab086b6..fdb6a16393b3 100644 --- a/ee/packages/presence/package.json +++ b/ee/packages/presence/package.json @@ -28,6 +28,9 @@ "files": [ "/dist" ], + "volta": { + "extends": "../../../package.json" + }, "dependencies": { "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 4cce8aebe07b..3492cc1f77bf 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -30,6 +30,9 @@ "files": [ "/dist" ], + "volta": { + "extends": "../../package.json" + }, "dependencies": { "@rocket.chat/apps-engine": "1.41.0-alpha.290", "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/core-services/src/lib/Api.ts b/packages/core-services/src/lib/Api.ts index 66806dc54fde..f0b5e67594c2 100644 --- a/packages/core-services/src/lib/Api.ts +++ b/packages/core-services/src/lib/Api.ts @@ -46,7 +46,11 @@ export class Api implements IApiService { } async broadcast(event: T, ...args: Parameters): Promise { - return this.broker?.broadcast(event, ...args); + if (!this.broker) { + throw new Error(`No broker set to broadcast: ${event}`); + } + + return this.broker.broadcast(event, ...args); } async broadcastToServices( From 3979680e8fb2eb735a5b3bdb5f9839eff79d3bff Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:33:14 -0300 Subject: [PATCH 7/7] regression: unmarked dangling promise on license validation (#30557) --- ee/packages/license/src/pendingLicense.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/packages/license/src/pendingLicense.ts b/ee/packages/license/src/pendingLicense.ts index 2c2140044336..8dd82dcd7774 100644 --- a/ee/packages/license/src/pendingLicense.ts +++ b/ee/packages/license/src/pendingLicense.ts @@ -8,10 +8,10 @@ export function setPendingLicense(this: LicenseManager, encryptedLicense: string } } -export function applyPendingLicense(this: LicenseManager) { +export async function applyPendingLicense(this: LicenseManager) { if (this.pendingLicense) { logger.info('Applying pending license.'); - this.setLicense(this.pendingLicense); + return this.setLicense(this.pendingLicense); } }