chore(apps): remove CI test dependency on marketplace api (#37272)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
pull/37219/head^2
Douglas Gubert 1 month ago committed by GitHub
parent ec22d9c102
commit de830b603c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      apps/meteor/ee/server/apps/appRequestsCron.ts
  2. 114
      apps/meteor/ee/server/apps/communication/rest.ts
  3. 6
      apps/meteor/ee/server/apps/cron.ts
  4. 103
      apps/meteor/ee/server/apps/marketplace/MarketplaceAPIClient.ts
  5. 22
      apps/meteor/ee/server/apps/marketplace/appInstall.ts
  6. 13
      apps/meteor/ee/server/apps/marketplace/fetchMarketplaceApps.ts
  7. 12
      apps/meteor/ee/server/apps/marketplace/fetchMarketplaceCategories.ts
  8. 8
      apps/meteor/ee/server/apps/marketplace/isTesting.ts
  9. 22
      apps/meteor/ee/server/apps/orchestrator.js
  10. 10
      apps/meteor/tests/unit/app/apps/server/mocks/orchestrator.mock.js

@ -1,5 +1,4 @@
import { cronJobs } from '@rocket.chat/cron';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { appRequestNotififyForUsers } from './marketplace/appRequestNotifyUsers';
import { Apps } from './orchestrator';
@ -21,20 +20,14 @@ const appsNotifyAppRequests = async function _appsNotifyAppRequests() {
return;
}
const baseUrl = Apps.getMarketplaceUrl();
if (!baseUrl) {
Apps.debugLog(`could not load marketplace base url to send app requests notifications`);
return;
}
const options = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const pendingSentUrl = `${baseUrl}/v1/app-request/sent/pending`;
const result = await fetch(pendingSentUrl, options);
const pendingSentUrl = `v1/app-request/sent/pending`;
const result = await Apps.getMarketplaceClient().fetch(pendingSentUrl, options);
const { data } = await result.json();
const filtered = installedApps.filter((app) => data.indexOf(app.getID()) !== -1);
@ -42,10 +35,10 @@ const appsNotifyAppRequests = async function _appsNotifyAppRequests() {
const appId = app.getID();
const appName = app.getName();
const usersNotified = await appRequestNotififyForUsers(baseUrl, workspaceUrl, appId, appName)
const usersNotified = await appRequestNotififyForUsers(Apps.getMarketplaceClient().getMarketplaceUrl(), workspaceUrl, appId, appName)
.then(async (response) => {
// Mark all app requests as sent
await fetch(`${baseUrl}/v1/app-request/markAsSent/${appId}`, { ...options, method: 'POST' });
await Apps.getMarketplaceClient().fetch(`v1/app-request/markAsSent/${appId}`, { ...options, method: 'POST' });
return response;
})
.catch((err) => {

@ -34,7 +34,7 @@ import { AppsEngineNoNodesFoundError } from '../../../../server/services/apps-en
import { canEnableApp } from '../../../app/license/server/canEnableApp';
import { fetchAppsStatusFromCluster } from '../../../lib/misc/fetchAppsStatusFromCluster';
import { formatAppInstanceForRest } from '../../../lib/misc/formatAppInstanceForRest';
import { notifyAppInstall } from '../marketplace/appInstall';
import { notifyMarketplace } from '../marketplace/appInstall';
import { fetchMarketplaceApps } from '../marketplace/fetchMarketplaceApps';
import { fetchMarketplaceCategories } from '../marketplace/fetchMarketplaceCategories';
import { MarketplaceAppsError, MarketplaceConnectionError, MarketplaceUnsupportedVersionError } from '../marketplace/marketplaceErrors';
@ -120,7 +120,7 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const baseUrl = orchestrator.getMarketplaceClient().getMarketplaceUrl();
const workspaceId = settings.get('Cloud_Workspace_Id');
const { action, appId, appVersion } = this.queryParams;
@ -193,7 +193,7 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const baseUrl = orchestrator.getMarketplaceClient().getMarketplaceUrl();
const workspaceId = settings.get('Cloud_Workspace_Id');
@ -254,8 +254,6 @@ export class AppsRestApi {
{ authRequired: true, permissionsRequired: ['manage-apps'] },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
// Gets the Apps from the marketplace
if ('marketplace' in this.queryParams && this.queryParams.marketplace) {
apiDeprecationLogger.endpoint(this.route, '7.0.0', this.response, 'Use /apps/marketplace to get the apps list.');
@ -327,6 +325,8 @@ export class AppsRestApi {
const seats = await Users.getActiveLocalUserCount();
const baseUrl = orchestrator.getMarketplaceClient().getMarketplaceUrl();
return API.v1.success({
url: `${baseUrl}/apps/${this.queryParams.appId}/${
this.queryParams.purchaseType === 'buy' ? this.queryParams.purchaseType : subscribeRoute
@ -361,27 +361,29 @@ export class AppsRestApi {
return API.v1.internalError();
}
} else if ('appId' in this.bodyParams && this.bodyParams.appId && this.bodyParams.marketplace && this.bodyParams.version) {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
try {
const downloadToken = await getWorkspaceAccessToken(true, 'marketplace:download', false);
const marketplaceToken = await getWorkspaceAccessToken();
const [downloadResponse, marketplaceResponse] = await Promise.all([
fetch(`${baseUrl}/v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${downloadToken}`, {
headers,
}).catch((cause) => {
throw new Error('App package download failed', { cause });
}),
fetch(`${baseUrl}/v1/apps/${this.bodyParams.appId}?appVersion=${this.bodyParams.version}`, {
headers: {
Authorization: `Bearer ${marketplaceToken}`,
...headers,
},
}).catch((cause) => {
throw new Error('App metadata download failed', { cause });
}),
Apps.getMarketplaceClient()
.fetch(`v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${downloadToken}`, {
headers,
})
.catch((cause) => {
throw new Error('App package download failed', { cause });
}),
Apps.getMarketplaceClient()
.fetch(`v1/apps/${this.bodyParams.appId}?appVersion=${this.bodyParams.version}`, {
headers: {
Authorization: `Bearer ${marketplaceToken}`,
...headers,
},
})
.catch((cause) => {
throw new Error('App metadata download failed', { cause });
}),
]);
if (downloadResponse.headers.get('content-type') !== 'application/zip') {
@ -469,7 +471,7 @@ export class AppsRestApi {
info.status = await aff.getApp().getStatus();
void notifyAppInstall(orchestrator.getMarketplaceUrl() as string, 'install', info);
void notifyMarketplace('install', info);
try {
await canEnableApp(aff.getApp().getStorageItem());
@ -500,7 +502,7 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Invalid request. Please ensure an appId is attached to the request.' });
}
const baseUrl = orchestrator.getMarketplaceUrl();
const baseUrl = orchestrator.getMarketplaceClient().getMarketplaceUrl();
const workspaceId = settings.get<string>('Cloud_Workspace_Id');
const requester = {
@ -610,8 +612,6 @@ export class AppsRestApi {
{ authRequired: true, permissionsRequired: ['manage-apps'] },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers: Record<string, any> = {};
const token = await getWorkspaceAccessToken();
if (token) {
@ -620,7 +620,7 @@ export class AppsRestApi {
let result;
try {
const request = await fetch(`${baseUrl}/v1/bundles/${this.urlParams.id}/apps`, { headers });
const request = await orchestrator.getMarketplaceClient().fetch(`v1/bundles/${this.urlParams.id}/apps`, { headers });
if (request.status !== 200) {
orchestrator.getRocketChatLogger().error("Error getting the Bundle's Apps from the Marketplace:", await request.json());
return API.v1.failure();
@ -641,8 +641,6 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken();
if (token) {
@ -651,7 +649,7 @@ export class AppsRestApi {
let result;
try {
const request = await fetch(`${baseUrl}/v1/featured-apps`, { headers });
const request = await orchestrator.getMarketplaceClient().fetch(`v1/featured-apps`, { headers });
if (request.status !== 200) {
orchestrator.getRocketChatLogger().error('Error getting the Featured Apps from the Marketplace:', await request.json());
return API.v1.failure();
@ -671,7 +669,6 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const { appId, q = '', sort = '', limit = 25, offset = 0 } = this.queryParams;
const headers = getDefaultHeaders();
@ -681,9 +678,11 @@ export class AppsRestApi {
}
try {
const request = await fetch(`${baseUrl}/v1/app-request?appId=${appId}&q=${q}&sort=${sort}&limit=${limit}&offset=${offset}`, {
headers,
});
const request = await orchestrator
.getMarketplaceClient()
.fetch(`v1/app-request?appId=${appId}&q=${q}&sort=${sort}&limit=${limit}&offset=${offset}`, {
headers,
});
const result = await request.json();
if (!request.ok) {
@ -704,7 +703,6 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken();
@ -713,7 +711,7 @@ export class AppsRestApi {
}
try {
const request = await fetch(`${baseUrl}/v1/app-request/stats`, { headers });
const request = await orchestrator.getMarketplaceClient().fetch(`v1/app-request/stats`, { headers });
const result = await request.json();
if (!request.ok) {
throw new Error(result.error);
@ -733,7 +731,6 @@ export class AppsRestApi {
{ authRequired: true },
{
async post() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken();
@ -744,7 +741,7 @@ export class AppsRestApi {
const { unseenRequests } = this.bodyParams;
try {
const request = await fetch(`${baseUrl}/v1/app-request/markAsSeen`, {
const request = await orchestrator.getMarketplaceClient().fetch(`v1/app-request/markAsSeen`, {
method: 'POST',
headers,
body: { ids: unseenRequests },
@ -808,8 +805,6 @@ export class AppsRestApi {
{
async get() {
if (this.queryParams.marketplace && this.queryParams.version) {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers: Record<string, any> = {}; // DO NOT ATTACH THE FRAMEWORK/ENGINE VERSION HERE.
const token = await getWorkspaceAccessToken();
if (token) {
@ -818,7 +813,9 @@ export class AppsRestApi {
let result: any;
try {
const request = await fetch(`${baseUrl}/v1/apps/${this.urlParams.id}?appVersion=${this.queryParams.version}`, { headers });
const request = await orchestrator
.getMarketplaceClient()
.fetch(`v1/apps/${this.urlParams.id}?appVersion=${this.queryParams.version}`, { headers });
if (request.status !== 200) {
orchestrator.getRocketChatLogger().error('Error getting the App information from the Marketplace:', await request.json());
return API.v1.failure();
@ -832,8 +829,6 @@ export class AppsRestApi {
}
if (this.queryParams.marketplace && this.queryParams.update && this.queryParams.appVersion) {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken();
if (token) {
@ -842,9 +837,11 @@ export class AppsRestApi {
let result;
try {
const request = await fetch(`${baseUrl}/v1/apps/${this.urlParams.id}/latest?appVersion=${this.queryParams.appVersion}`, {
headers,
});
const request = await orchestrator
.getMarketplaceClient()
.fetch(`v1/apps/${this.urlParams.id}/latest?appVersion=${this.queryParams.appVersion}`, {
headers,
});
if (request.status !== 200) {
orchestrator.getRocketChatLogger().error('Error getting the App update info from the Marketplace:', await request.json());
return API.v1.failure();
@ -881,18 +878,15 @@ export class AppsRestApi {
buff = Buffer.from(await response.arrayBuffer());
} else if (this.bodyParams.appId && this.bodyParams.marketplace && this.bodyParams.version) {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken(true, 'marketplace:download', false);
try {
const response = await fetch(
`${baseUrl}/v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${token}`,
{
const response = await orchestrator
.getMarketplaceClient()
.fetch(`v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${token}`, {
headers,
},
);
});
if (response.status !== 200) {
orchestrator.getRocketChatLogger().error('Error getting the App from the Marketplace:', await response.text());
@ -976,7 +970,7 @@ export class AppsRestApi {
info.status = await aff.getApp().getStatus();
void notifyAppInstall(orchestrator.getMarketplaceUrl() as string, 'update', info);
void notifyMarketplace('update', info);
void orchestrator.getNotifier().appUpdated(info.id);
@ -1007,7 +1001,7 @@ export class AppsRestApi {
return API.v1.failure({ app: info });
}
void notifyAppInstall(orchestrator.getMarketplaceUrl() as string, 'uninstall', info);
void notifyMarketplace('uninstall', info);
return API.v1.success({ app: info });
},
@ -1019,8 +1013,6 @@ export class AppsRestApi {
{ authRequired: true },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers: Record<string, any> = {}; // DO NOT ATTACH THE FRAMEWORK/ENGINE VERSION HERE.
const token = await getWorkspaceAccessToken();
if (token) {
@ -1030,7 +1022,7 @@ export class AppsRestApi {
let result;
let statusCode;
try {
const request = await fetch(`${baseUrl}/v1/apps/${this.urlParams.id}`, { headers });
const request = await orchestrator.getMarketplaceClient().fetch(`v1/apps/${this.urlParams.id}`, { headers });
statusCode = request.status;
result = await request.json();
@ -1056,8 +1048,6 @@ export class AppsRestApi {
{ authRequired: true, permissionsRequired: ['manage-apps'] },
{
async post() {
const baseUrl = orchestrator.getMarketplaceUrl();
const headers = getDefaultHeaders();
const token = await getWorkspaceAccessToken();
if (token) {
@ -1072,7 +1062,10 @@ export class AppsRestApi {
let result;
let statusCode;
try {
const request = await fetch(`${baseUrl}/v1/workspaces/${workspaceIdSetting.value}/apps/${this.urlParams.id}`, { headers });
const request = await orchestrator
.getMarketplaceClient()
.fetch(`v1/workspaces/${workspaceIdSetting.value}/apps/${this.urlParams.id}`, { headers });
statusCode = request.status;
result = await request.json();
@ -1132,12 +1125,11 @@ export class AppsRestApi {
{ authRequired: false },
{
async get() {
const baseUrl = orchestrator.getMarketplaceUrl();
const appId = this.urlParams.id;
const headers = getDefaultHeaders();
try {
const request = await fetch(`${baseUrl}/v1/apps/${appId}/screenshots`, { headers });
const request = await orchestrator.getMarketplaceClient().fetch(`v1/apps/${appId}/screenshots`, { headers });
const data = await request.json();
return API.v1.success({

@ -2,7 +2,6 @@ import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import type { ProxiedApp } from '@rocket.chat/apps-engine/server/ProxiedApp';
import { cronJobs } from '@rocket.chat/cron';
import { Settings, Users } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { Apps } from './orchestrator';
import { getWorkspaceAccessToken } from '../../../app/cloud/server';
@ -77,12 +76,11 @@ const notifyAdminsAboutRenewedApps = async function _notifyAdminsAboutRenewedApp
const appsUpdateMarketplaceInfo = async function _appsUpdateMarketplaceInfo() {
const token = await getWorkspaceAccessToken();
const baseUrl = Apps.getMarketplaceUrl();
const workspaceIdSetting = await Settings.getValueById('Cloud_Workspace_Id');
const currentSeats = await Users.getActiveLocalUserCount();
const fullUrl = `${baseUrl}/v1/workspaces/${workspaceIdSetting}/apps`;
const fullUrl = `v1/workspaces/${workspaceIdSetting}/apps`;
const options = {
headers: {
Authorization: `Bearer ${token}`,
@ -95,7 +93,7 @@ const appsUpdateMarketplaceInfo = async function _appsUpdateMarketplaceInfo() {
let data = [];
try {
const response = await fetch(fullUrl, options);
const response = await Apps.getMarketplaceClient().fetch(fullUrl, options);
const result = await response.json();

@ -0,0 +1,103 @@
import { type ExtendedFetchOptions, Response, serverFetch } from '@rocket.chat/server-fetch';
import { isTesting } from './isTesting';
export class MarketplaceAPIClient {
#fetchStrategy: (input: string, options?: ExtendedFetchOptions, allowSelfSignedCerts?: boolean) => Promise<Response>;
#marketplaceUrl: string;
constructor() {
if (typeof process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL === 'string' && process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL !== '') {
this.#marketplaceUrl = process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL;
} else {
this.#marketplaceUrl = 'https://marketplace.rocket.chat';
}
if (isTesting()) {
this.#fetchStrategy = mockMarketplaceFetch;
} else {
this.#fetchStrategy = serverFetch;
}
}
public getMarketplaceUrl(): string {
return this.#marketplaceUrl;
}
public setStrategy(strategyName: 'default' | 'mock'): void {
switch (strategyName) {
case 'default':
this.#fetchStrategy = serverFetch;
break;
case 'mock':
this.#fetchStrategy = mockMarketplaceFetch;
break;
default:
throw new Error('Unknown strategy');
}
}
public fetch(input: string, options?: ExtendedFetchOptions, allowSelfSignedCerts?: boolean): ReturnType<typeof serverFetch> {
if (!input.startsWith('http://') && !input.startsWith('https://')) {
input = this.getMarketplaceUrl().concat(!input.startsWith('/') ? '/' : '', input);
}
return this.#fetchStrategy(input, options, allowSelfSignedCerts);
}
}
/**
* Provide mocked HTTP responses for supported Marketplace API endpoints.
*
* This allows us to prevent actual calls to Marketplace service
* during TEST_MODE (CI, local tests, etc.), i.e., remove our dependency
* an external unrelated service
*
* The response content provided has minimal structure to allow for the program
* to not crash by receiving something different from the expected structure
*
* @param input - The request URL or path used to determine which mock response to return
* @returns A `Response` with status 200 and a JSON body corresponding to the requested marketplace endpoint
* @throws Error when `input` does not match any supported mock endpoint
*/
function mockMarketplaceFetch(input: string, _options?: ExtendedFetchOptions, _allowSelfSignedCerts?: boolean): Promise<Response> {
let content: string;
switch (true) {
// This is not an exhaustive list of endpoints
case input.indexOf('v1/apps') !== -1:
case input.indexOf('v1/categories') !== -1:
case input.indexOf('v1/bundles') !== -1:
content = '[]';
break;
case input.indexOf('v1/featured-apps') !== -1:
content = '{"sections":[]}';
break;
case input.indexOf('v1/app-request/stats') !== -1:
content = '{"data":{"totalSeen":0,"totalUnseen":0}}';
break;
case input.indexOf('v1/app-request/markAsSeen') !== -1:
content = '{"success":false}';
break;
case input.indexOf('v1/app-request') !== -1:
content = '{"data":[],"meta":{"limit":25,"offset":0,"sort":"","filter":"","total":0}}';
break;
case input.indexOf('v1/workspaces') !== -1:
content = '{}';
break;
default:
throw new Error(`Invalid marketplace mock request ${input}`);
}
const response = new Response(Buffer.from(content), {
headers: {
'content-type': 'application/json',
},
status: 200,
});
return Promise.resolve(response);
}

@ -1,13 +1,21 @@
import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { getWorkspaceAccessToken } from '../../../../app/cloud/server';
import { settings } from '../../../../app/settings/server';
import { Info } from '../../../../app/utils/rocketchat.info';
type installAction = 'install' | 'update' | 'uninstall';
export async function notifyAppInstall(marketplaceBaseUrl: string, action: installAction, appInfo: IAppInfo): Promise<void> {
import { Apps } from '../orchestrator';
type MarketplaceNotificationType = 'install' | 'update' | 'uninstall';
/**
* Notify the marketplace about an app install, update, or uninstall event.
*
* Attempts to POST a notification to the marketplace client at `v1/apps/{id}/install` containing the action, app metadata, Rocket.Chat and engine versions, and site URL. If a workspace access token is available it is included in the Authorization header. Any errors encountered while obtaining the token, reading settings, or sending the request are ignored.
*
* @param action - The marketplace event type: 'install', 'update', or 'uninstall'
* @param appInfo - App metadata (including `id`, `name`, `nameSlug`, and `version`) to include in the notification
*/
export async function notifyMarketplace(action: MarketplaceNotificationType, appInfo: IAppInfo): Promise<void> {
const headers: { Authorization?: string } = {};
try {
@ -34,10 +42,10 @@ export async function notifyAppInstall(marketplaceBaseUrl: string, action: insta
siteUrl,
};
const pendingSentUrl = `${marketplaceBaseUrl}/v1/apps/${appInfo.id}/install`;
const pendingSentUrl = `v1/apps/${appInfo.id}/install`;
try {
await fetch(pendingSentUrl, {
await Apps.getMarketplaceClient().fetch(pendingSentUrl, {
method: 'POST',
headers,
body: data,

@ -1,5 +1,4 @@
import type { App } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { z } from 'zod';
import { getMarketplaceHeaders } from './getMarketplaceHeaders';
@ -130,8 +129,16 @@ const fetchMarketplaceAppsSchema = z.array(
}),
);
/**
* Fetches marketplace apps available to the workspace.
*
* @param endUserID - Optional end-user identifier used to filter apps returned by the marketplace.
* @returns An array of marketplace `App` objects describing available apps and their latest release metadata.
* @throws MarketplaceConnectionError when the marketplace cannot be reached.
* @throws MarketplaceUnsupportedVersionError when the marketplace reports an unsupported client version.
* @throws MarketplaceAppsError for marketplace-side errors, including invalid apps engine version, internal marketplace errors, or a generic failure to fetch apps.
*/
export async function fetchMarketplaceApps({ endUserID }: FetchMarketplaceAppsParams = {}): Promise<App[]> {
const baseUrl = Apps.getMarketplaceUrl();
const headers = getMarketplaceHeaders();
const token = await getWorkspaceAccessToken();
if (token) {
@ -140,7 +147,7 @@ export async function fetchMarketplaceApps({ endUserID }: FetchMarketplaceAppsPa
let request;
try {
request = await fetch(`${baseUrl}/v1/apps`, {
request = await Apps.getMarketplaceClient().fetch(`v1/apps`, {
headers,
params: {
...(endUserID && { endUserID }),

@ -1,5 +1,4 @@
import type { AppCategory } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { z } from 'zod';
import { getMarketplaceHeaders } from './getMarketplaceHeaders';
@ -18,8 +17,15 @@ const fetchMarketplaceCategoriesSchema = z.array(
}),
);
/**
* Fetches marketplace categories from the marketplace API.
*
* @returns An array of marketplace categories (`AppCategory[]`).
* @throws MarketplaceConnectionError when the HTTP request cannot be made.
* @throws MarketplaceUnsupportedVersionError when the marketplace responds with status 426 and `errorMsg` equals `"unsupported version"`.
* @throws MarketplaceAppsError when the marketplace returns an internal error (specific internal codes) or any other non-successful response.
*/
export async function fetchMarketplaceCategories(): Promise<AppCategory[]> {
const baseUrl = Apps.getMarketplaceUrl();
const headers = getMarketplaceHeaders();
const token = await getWorkspaceAccessToken();
if (token) {
@ -28,7 +34,7 @@ export async function fetchMarketplaceCategories(): Promise<AppCategory[]> {
let request;
try {
request = await fetch(`${baseUrl}/v1/categories`, { headers });
request = await Apps.getMarketplaceClient().fetch(`v1/categories`, { headers });
} catch (error) {
throw new MarketplaceConnectionError('Marketplace_Bad_Marketplace_Connection');
}

@ -0,0 +1,8 @@
/**
* Determine whether the application is running in testing mode.
*
* @returns `true` if the `TEST_MODE` environment variable equals `'true'`, `false` otherwise.
*/
export function isTesting() {
return process.env.TEST_MODE === 'true';
}

@ -6,6 +6,8 @@ import { AppLogs, Apps as AppsModel, AppsPersistence, Statistics } from '@rocket
import { Meteor } from 'meteor/meteor';
import { AppServerNotifier, AppsRestApi, AppUIKitInteractionApi } from './communication';
import { MarketplaceAPIClient } from './marketplace/MarketplaceAPIClient';
import { isTesting } from './marketplace/isTesting';
import { AppRealLogStorage, AppRealStorage, ConfigurableAppSourceStorage } from './storage';
import { RealAppBridges } from '../../../app/apps/server/bridges';
import {
@ -24,15 +26,13 @@ import { AppThreadsConverter } from '../../../app/apps/server/converters/threads
import { settings } from '../../../app/settings/server';
import { canEnableApp } from '../../app/license/server/canEnableApp';
function isTesting() {
return process.env.TEST_MODE === 'true';
}
const DISABLED_PRIVATE_APP_INSTALLATION = ['yes', 'true'].includes(String(process.env.DISABLE_PRIVATE_APP_INSTALLATION).toLowerCase());
export class AppServerOrchestrator {
constructor() {
this._isInitialized = false;
this.marketplaceClient = new MarketplaceAPIClient();
}
initialize() {
@ -42,12 +42,6 @@ export class AppServerOrchestrator {
this._rocketchatLogger = new Logger('Rocket.Chat Apps');
if (typeof process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL === 'string' && process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL !== '') {
this._marketplaceUrl = process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL;
} else {
this._marketplaceUrl = 'https://marketplace.rocket.chat';
}
this._model = AppsModel;
this._logModel = AppLogs;
this._persistModel = AppsPersistence;
@ -89,6 +83,10 @@ export class AppServerOrchestrator {
this._isInitialized = true;
}
getMarketplaceClient() {
return this.marketplaceClient;
}
getModel() {
return this._model;
}
@ -169,10 +167,6 @@ export class AppServerOrchestrator {
}
}
getMarketplaceUrl() {
return this._marketplaceUrl;
}
async load() {
// Don't try to load it again if it has
// already been loaded

@ -2,7 +2,7 @@
export class AppServerOrchestratorMock {
constructor() {
this._marketplaceUrl = 'https://marketplace.rocket.chat';
this._marketplaceClient = {};
this._model = {};
this._logModel = {};
@ -26,6 +26,10 @@ export class AppServerOrchestratorMock {
this._communicators.set('restapi', {});
}
getMarketplaceClient() {
return this._marketplaceClient;
}
getModel() {
return this._model;
}
@ -76,10 +80,6 @@ export class AppServerOrchestratorMock {
}
}
getMarketplaceUrl() {
return this._marketplaceUrl;
}
load() {
// Don't try to load it again if it has
// already been loaded

Loading…
Cancel
Save