diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts index 5a8251ff8e9dc..ff45b5eafee1f 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts @@ -41,6 +41,33 @@ export async function getWorkspaceAccessToken(forceNew = false, scope = '', save return accessToken.token; } +export class CloudWorkspaceAccessTokenError extends Error { + constructor() { + super('Could not get workspace access token'); + } +} + +export async function getWorkspaceAccessTokenOrThrow(forceNew = false, scope = '', save = true): Promise { + const token = await getWorkspaceAccessToken(forceNew, scope, save); + + if (!token) { + throw new CloudWorkspaceAccessTokenError(); + } + + return token; +} + +export const generateWorkspaceBearerHttpHeaderOrThrow = async ( + forceNew = false, + scope = '', + save = true, +): Promise<{ Authorization: string }> => { + const token = await getWorkspaceAccessTokenOrThrow(forceNew, scope, save); + return { + Authorization: `Bearer ${token}`, + }; +}; + export const generateWorkspaceBearerHttpHeader = async ( forceNew = false, scope = '', diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts index 275e646e53430..6be18f86d4663 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts @@ -5,64 +5,52 @@ import { callbacks } from '../../../../lib/callbacks'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { settings } from '../../../settings/server'; import { LICENSE_VERSION } from '../license'; -import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; +import { generateWorkspaceBearerHttpHeaderOrThrow } from './getWorkspaceAccessToken'; +import { handleResponse } from './supportedVersionsToken/supportedVersionsToken'; -export async function getWorkspaceLicense(): Promise<{ updated: boolean; license: string }> { - const currentLicense = await Settings.findOne('Cloud_Workspace_License'); - - const cachedLicenseReturn = async () => { - const license = currentLicense?.value as string; - if (license) { - await callbacks.run('workspaceLicenseChanged', license); - } +export async function getWorkspaceLicense() { + const token = await generateWorkspaceBearerHttpHeaderOrThrow(); - return { updated: false, license }; - }; + const currentLicense = await Settings.findOne('Cloud_Workspace_License'); - const token = await getWorkspaceAccessToken(); - if (!token) { - return cachedLicenseReturn(); + // TODO: check if this is the correct way to handle this + // If there is no license, in theory, it should be a new workspace non registered + // in this case the `generateWorkspaceBearerHttpHeaderOrThrow` show throw an error before + // so in theory, this should never happen + if (!currentLicense?._updatedAt) { + throw new Error('Failed to retrieve current license'); } - let licenseResult; - try { - const request = await fetch(`${settings.get('Cloud_Workspace_Registration_Client_Uri')}/license`, { + const request = await handleResponse( + fetch(`${settings.get('Cloud_Workspace_Registration_Client_Uri')}/license`, { headers: { - Authorization: `Bearer ${token}`, + ...token, }, params: { version: LICENSE_VERSION, }, - }); - - if (!request.ok) { - throw new Error((await request.json()).error); - } + }), + ); - licenseResult = await request.json(); - } catch (err: any) { + if (!request.success) { SystemLogger.error({ msg: 'Failed to update license from Rocket.Chat Cloud', url: '/license', - err, + err: request.error, }); - - return cachedLicenseReturn(); + if (currentLicense.value) { + return callbacks.run('workspaceLicenseChanged', currentLicense.value); + } + return; } - const remoteLicense = licenseResult; - - if (!currentLicense || !currentLicense._updatedAt) { - throw new Error('Failed to retrieve current license'); - } + const remoteLicense = request.result as any; if (remoteLicense.updatedAt <= currentLicense._updatedAt) { - return cachedLicenseReturn(); + return callbacks.run('workspaceLicenseChanged', currentLicense.value); } await Settings.updateValueById('Cloud_Workspace_License', remoteLicense.license); await callbacks.run('workspaceLicenseChanged', remoteLicense.license); - - return { updated: true, license: remoteLicense.license }; } diff --git a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts index 1f0ef718e33db..e868e5ec83b76 100644 --- a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts +++ b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts @@ -36,7 +36,10 @@ export const handleResponse = async (promise: Promise) => { (async () => { const request = await promise; if (!request.ok) { - throw new Error((await request.json()).error); + if (request.size > 0) { + throw new Error((await request.json()).error); + } + throw new Error(request.statusText); } return request.json(); diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace.ts deleted file mode 100644 index ef2cfc07ca1fe..0000000000000 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { NPS, Banner } from '@rocket.chat/core-services'; -import { Settings } from '@rocket.chat/models'; -import { serverFetch as fetch } from '@rocket.chat/server-fetch'; - -import { SystemLogger } from '../../../../server/lib/logger/system'; -import { getAndCreateNpsSurvey } from '../../../../server/services/nps/getAndCreateNpsSurvey'; -import { settings } from '../../../settings/server'; -import { buildWorkspaceRegistrationData } from './buildRegistrationData'; -import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; -import { getWorkspaceLicense } from './getWorkspaceLicense'; -import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; -import { getCachedSupportedVersionsToken } from './supportedVersionsToken/supportedVersionsToken'; - -export async function syncWorkspace() { - const { workspaceRegistered, connectToCloud } = await retrieveRegistrationStatus(); - if (!workspaceRegistered || !connectToCloud) { - return false; - } - - const info = await buildWorkspaceRegistrationData(undefined); - - const workspaceUrl = settings.get('Cloud_Workspace_Registration_Client_Uri'); - - let result; - try { - const headers: Record = {}; - const token = await getWorkspaceAccessToken(true); - - if (token) { - headers.Authorization = `Bearer ${token}`; - } else { - return false; - } - - const request = await fetch(`${workspaceUrl}/client`, { - headers, - body: info, - method: 'POST', - }); - - if (!request.ok) { - throw new Error((await request.json()).error); - } - - result = await request.json(); - } catch (err: any) { - SystemLogger.error({ - msg: 'Failed to sync with Rocket.Chat Cloud', - url: '/client', - err, - }); - - return false; - } finally { - // aways fetch the license - await getWorkspaceLicense(); - } - - const data = result; - if (!data) { - return true; - } - - if (data.publicKey) { - await Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey); - } - - if (data.trial?.trialId) { - await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); - } - - if (data.nps) { - const { id: npsId, expireAt } = data.nps; - - const startAt = new Date(data.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); - } - } - - // add banners - if (data.banners) { - for await (const banner of data.banners) { - const { createdAt, expireAt, startAt } = banner; - - await Banner.create({ - ...banner, - createdAt: new Date(createdAt), - expireAt: new Date(expireAt), - startAt: new Date(startAt), - }); - } - } - - await getCachedSupportedVersionsToken.reset(); - return true; -} diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts new file mode 100644 index 0000000000000..48d5afa9dbc5a --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts @@ -0,0 +1,17 @@ +import { CloudWorkspaceAccessTokenError } from '../getWorkspaceAccessToken'; +import { getWorkspaceLicense } from '../getWorkspaceLicense'; +import { getCachedSupportedVersionsToken } from '../supportedVersionsToken/supportedVersionsToken'; +import { syncCloudData } from './syncCloudData'; + +export async function syncWorkspace() { + try { + await syncCloudData(); + await getWorkspaceLicense(); + } catch (error) { + if (error instanceof CloudWorkspaceAccessTokenError) { + // TODO: Remove License if there is no access token + } + } + + await getCachedSupportedVersionsToken.reset(); +} diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts new file mode 100644 index 0000000000000..0dc56f31c5da4 --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts @@ -0,0 +1,85 @@ +import { NPS, Banner } from '@rocket.chat/core-services'; +import { Settings } from '@rocket.chat/models'; +import { serverFetch as fetch } from '@rocket.chat/server-fetch'; + +import { SystemLogger } from '../../../../../server/lib/logger/system'; +import { getAndCreateNpsSurvey } from '../../../../../server/services/nps/getAndCreateNpsSurvey'; +import { settings } from '../../../../settings/server'; +import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; +import { generateWorkspaceBearerHttpHeaderOrThrow } from '../getWorkspaceAccessToken'; +import { handleResponse } from '../supportedVersionsToken/supportedVersionsToken'; + +export async function syncCloudData() { + const info = await buildWorkspaceRegistrationData(undefined); + + const token = await generateWorkspaceBearerHttpHeaderOrThrow(true); + + const request = await handleResponse( + fetch(`${settings.get('Cloud_Workspace_Registration_Client_Uri')}/client`, { + headers: { + ...token, + }, + body: info, + method: 'POST', + }), + ); + + if (!request.success) { + return SystemLogger.error({ + msg: 'Failed to sync with Rocket.Chat Cloud', + url: '/client', + err: request.error, + }); + } + + const data = request.result as any; + if (!data) { + return true; + } + + if (data.publicKey) { + await Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey); + } + + if (data.trial?.trialId) { + await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); + } + + if (data.nps) { + const { id: npsId, expireAt } = data.nps; + + const startAt = new Date(data.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); + } + } + + // add banners + if (data.banners) { + for await (const banner of data.banners) { + const { createdAt, expireAt, startAt } = banner; + + await Banner.create({ + ...banner, + createdAt: new Date(createdAt), + expireAt: new Date(expireAt), + startAt: new Date(startAt), + }); + } + } + + return true; +}