diff --git a/.changeset/bump-patch-1698348780628.md b/.changeset/bump-patch-1698348780628.md
new file mode 100644
index 00000000000..e1eaa7980af
--- /dev/null
+++ b/.changeset/bump-patch-1698348780628.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Bump @rocket.chat/meteor version.
diff --git a/.changeset/cuddly-animal-eat.md b/.changeset/cuddly-animal-eat.md
new file mode 100644
index 00000000000..41fa17b9cb3
--- /dev/null
+++ b/.changeset/cuddly-animal-eat.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixed widget's `nextAgent` API sending an array of chars instead of an object for `departmentId` parameter
diff --git a/.changeset/cuddly-ties-run.md b/.changeset/cuddly-ties-run.md
new file mode 100644
index 00000000000..cb387389984
--- /dev/null
+++ b/.changeset/cuddly-ties-run.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+fix: custom-css injection
diff --git a/.changeset/serious-cats-fetch.md b/.changeset/serious-cats-fetch.md
new file mode 100644
index 00000000000..4718d3597e5
--- /dev/null
+++ b/.changeset/serious-cats-fetch.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixed a problem that would prevent private apps from being shown on air-gapped environments
diff --git a/.changeset/serious-cats-run.md b/.changeset/serious-cats-run.md
new file mode 100644
index 00000000000..8f9e6618c85
--- /dev/null
+++ b/.changeset/serious-cats-run.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixed a problem caused by `onlyAvailable` property of `livechat/users/agent` endpoint not having the proper type validation
\ No newline at end of file
diff --git a/apps/meteor/.docker/Dockerfile b/apps/meteor/.docker/Dockerfile
index 650d3fc5fee..456ed4becaf 100644
--- a/apps/meteor/.docker/Dockerfile
+++ b/apps/meteor/.docker/Dockerfile
@@ -14,9 +14,11 @@ RUN groupadd -g 65533 -r rocketchat \
ADD --chown=rocketchat:rocketchat . /app
RUN aptMark="$(apt-mark showmanual)" \
- && apt-get install -y --no-install-recommends g++ make python ca-certificates \
+ && apt-get install -y --no-install-recommends g++ make python3 ca-certificates \
&& cd /app/bundle/programs/server \
&& npm install \
+ && cd npm/node_modules/isolated-vm \
+ && npm install \
&& apt-mark auto '.*' > /dev/null \
&& apt-mark manual $aptMark > /dev/null \
&& find /usr/local -type f -executable -exec ldd '{}' ';' \
diff --git a/apps/meteor/app/ui-master/server/index.js b/apps/meteor/app/ui-master/server/index.js
index f9e335451d7..34a9618d2db 100644
--- a/apps/meteor/app/ui-master/server/index.js
+++ b/apps/meteor/app/ui-master/server/index.js
@@ -120,8 +120,6 @@ Meteor.startup(() => {
})(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX);
injectIntoHead('base', ``);
-
- injectIntoHead('css-theme', '');
});
const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => {
diff --git a/apps/meteor/client/components/AutoCompleteAgent.tsx b/apps/meteor/client/components/AutoCompleteAgent.tsx
index f2cbebe4692..059ac4251cc 100644
--- a/apps/meteor/client/components/AutoCompleteAgent.tsx
+++ b/apps/meteor/client/components/AutoCompleteAgent.tsx
@@ -27,7 +27,7 @@ const AutoCompleteAgent = ({
haveAll = false,
haveNoAgentsSelectedOption = false,
excludeId,
- showIdleAgents = false,
+ showIdleAgents = true,
onlyAvailable = false,
withTitle = false,
onChange,
diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts
index e2f6f80f235..ef00a6c0b81 100644
--- a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts
+++ b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts
@@ -29,7 +29,7 @@ export const useAgentsList = (
const reload = useCallback(() => setItemsList(new RecordList()), []);
const getAgents = useEndpoint('GET', '/v1/livechat/users/agent');
- const { text, onlyAvailable = false, showIdleAgents = false, excludeId, haveAll, haveNoAgentsSelectedOption } = options;
+ const { text, onlyAvailable = false, showIdleAgents = true, excludeId, haveAll, haveNoAgentsSelectedOption } = options;
useComponentDidUpdate(() => {
options && reload();
diff --git a/apps/meteor/client/providers/AppsProvider.tsx b/apps/meteor/client/providers/AppsProvider.tsx
index 042ce836547..15512fad24c 100644
--- a/apps/meteor/client/providers/AppsProvider.tsx
+++ b/apps/meteor/client/providers/AppsProvider.tsx
@@ -66,24 +66,23 @@ const AppsProvider: FC = ({ children }) => {
},
{
staleTime: Infinity,
- keepPreviousData: true,
onSettled: () => queryClient.invalidateQueries(['marketplace', 'apps-stored']),
},
);
const store = useQuery(
- ['marketplace', 'apps-stored', isAdminUser],
+ ['marketplace', 'apps-stored', instance.data, marketplace.data],
() => {
- if (!marketplace.isSuccess || !instance.isSuccess) {
+ if (!marketplace.isFetched && !instance.isFetched) {
throw new Error('Apps not loaded');
}
const marketplaceApps: App[] = [];
const installedApps: App[] = [];
const privateApps: App[] = [];
- const clonedData = [...instance.data];
+ const clonedData = [...(instance.data || [])];
- sortByName(marketplace.data).forEach((app) => {
+ sortByName(marketplace.data || []).forEach((app) => {
const appIndex = clonedData.findIndex(({ id }) => id === app.id);
const [installedApp] = appIndex > -1 ? clonedData.splice(appIndex, 1) : [];
@@ -117,8 +116,7 @@ const AppsProvider: FC = ({ children }) => {
return [marketplaceApps, installedApps, privateApps];
},
{
- enabled: marketplace.isSuccess && instance.isSuccess && !instance.isRefetching,
- keepPreviousData: true,
+ enabled: marketplace.isFetched && instance.isFetched,
},
);
@@ -130,9 +128,9 @@ const AppsProvider: FC = ({ children }) => {
{
await Promise.all([queryClient.invalidateQueries(['marketplace'])]);
},
diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx
index 40d90b56e04..5e564fca59b 100644
--- a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx
+++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx
@@ -32,6 +32,7 @@ const AppsPageContent = (): ReactElement => {
const context = useRouteParameter('context');
const isMarketplace = context === 'explore';
+ const isPremium = context === 'premium';
const isRequested = context === 'requested';
const [freePaidFilterStructure, setFreePaidFilterStructure] = useState({
@@ -115,7 +116,8 @@ const AppsPageContent = (): ReactElement => {
const noInstalledApps = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.totalAppsLength === 0;
- const noMarketplaceOrInstalledAppMatches = appsResult.phase === AsyncStatePhase.RESOLVED && isMarketplace && appsResult.value.count === 0;
+ const noMarketplaceOrInstalledAppMatches =
+ appsResult.phase === AsyncStatePhase.RESOLVED && (isMarketplace || isPremium) && appsResult.value.count === 0;
const noInstalledAppMatches =
appsResult.phase === AsyncStatePhase.RESOLVED &&
@@ -123,7 +125,7 @@ const AppsPageContent = (): ReactElement => {
appsResult.value.totalAppsLength !== 0 &&
appsResult.value.count === 0;
- const noAppRequests = context === 'requested' && appsResult?.value?.totalAppsLength !== 0 && appsResult?.value?.count === 0;
+ const noAppRequests = context === 'requested' && appsResult?.value?.count === 0;
const noErrorsOcurred = !noMarketplaceOrInstalledAppMatches && !noInstalledAppMatches && !noInstalledApps && !noAppRequests;
@@ -168,6 +170,30 @@ const AppsPageContent = (): ReactElement => {
toggleInitialSortOption(isRequested);
}, [isMarketplace, isRequested, sortFilterOnSelected, t, toggleInitialSortOption]);
+ const getEmptyState = () => {
+ if (noAppRequests) {
+ return ;
+ }
+
+ if (noMarketplaceOrInstalledAppMatches) {
+ return ;
+ }
+
+ if (noInstalledAppMatches) {
+ return (
+
+ );
+ }
+
+ if (noInstalledApps) {
+ return context === 'private' ? : ;
+ }
+ };
+
return (
<>
{
noErrorsOcurred={noErrorsOcurred}
/>
)}
- {noAppRequests && }
- {noMarketplaceOrInstalledAppMatches && (
-
- )}
- {noInstalledAppMatches && (
-
- )}
- {noInstalledApps && <>{context === 'private' ? : }>}
+ {getEmptyState()}
{appsResult.phase === AsyncStatePhase.REJECTED && }
>
);
diff --git a/apps/meteor/client/views/marketplace/AppsPage/NoAppRequestsEmptyState.tsx b/apps/meteor/client/views/marketplace/AppsPage/NoAppRequestsEmptyState.tsx
index 230c52a7a9a..dc7d77e64a7 100644
--- a/apps/meteor/client/views/marketplace/AppsPage/NoAppRequestsEmptyState.tsx
+++ b/apps/meteor/client/views/marketplace/AppsPage/NoAppRequestsEmptyState.tsx
@@ -1,4 +1,4 @@
-import { States, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
+import { Box, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
@@ -6,10 +6,13 @@ const NoAppRequestsEmptyState = () => {
const t = useTranslation();
return (
-
- {t('No_requested_apps')}
- {t('Requested_apps_will_appear_here')}
-
+
+
+
+ {t('No_requested_apps')}
+ {t('Requested_apps_will_appear_here')}
+
+
);
};
diff --git a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx
index d52d7b92a77..b7fec778401 100644
--- a/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx
+++ b/apps/meteor/client/views/marketplace/AppsPage/PrivateEmptyState.tsx
@@ -8,7 +8,7 @@ const PrivateEmptyState = () => {
return (
-
+
{t('No_private_apps_installed')}
{t('Private_apps_are_side-loaded')}
diff --git a/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts b/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts
index 61ec512bd6e..108e695b6bd 100644
--- a/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts
+++ b/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts
@@ -8,10 +8,10 @@ const shouldHandleErrorAsWarning = (message: string): boolean => {
};
export const handleAPIError = (errorObject: unknown): void => {
- const { message = '', error = '' } = errorObject as { message?: string; error?: string };
+ const { error = '', message = error } = errorObject as { message?: string; error?: string };
if (shouldHandleErrorAsWarning(message)) {
- return dispatchToastMessage({ type: 'warning', message: t(message) });
+ return dispatchToastMessage({ type: 'error', message: t(message) });
}
dispatchToastMessage({ type: 'error', message: t(`Apps_Error_${error}`) });
diff --git a/apps/meteor/ee/server/apps/communication/rest.ts b/apps/meteor/ee/server/apps/communication/rest.ts
index 1203d0d8c91..4f8bd0026a1 100644
--- a/apps/meteor/ee/server/apps/communication/rest.ts
+++ b/apps/meteor/ee/server/apps/communication/rest.ts
@@ -164,8 +164,7 @@ export class AppsRestApi {
}
result = await request.json();
} catch (e: any) {
- orchestrator.getRocketChatLogger().error('Error getting the categories from the Marketplace:', e.response.data);
- return API.v1.internalError();
+ return handleError('Unable to access Marketplace. Does the server has access to the internet?', e);
}
return API.v1.success(result);
diff --git a/apps/meteor/tests/data/users.helper.js b/apps/meteor/tests/data/users.helper.js
index 92425902cb5..47d26f83c5b 100644
--- a/apps/meteor/tests/data/users.helper.js
+++ b/apps/meteor/tests/data/users.helper.js
@@ -1,3 +1,4 @@
+import { UserStatus } from '@rocket.chat/core-typings';
import { api, credentials, request } from './api-data';
import { password } from './user';
@@ -89,3 +90,9 @@ export const setUserActiveStatus = (userId, activeStatus = true) =>
})
.end(resolve);
});
+
+export const setUserStatus = (overrideCredentials = credentials, status = UserStatus.ONLINE) =>
+ request.post(api('users.setStatus')).set(overrideCredentials).send({
+ message: '',
+ status,
+ });
diff --git a/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts b/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
index 6f9120ea909..044cdf498a8 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
@@ -1,4 +1,4 @@
-import type { ILivechatAgent, ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
+import { UserStatus, type ILivechatAgent, type ILivechatDepartment, type IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
@@ -16,7 +16,7 @@ import {
} from '../../../data/livechat/rooms';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
-import { createUser, deleteUser, getMe, login } from '../../../data/users.helper';
+import { createUser, deleteUser, getMe, login, setUserStatus } from '../../../data/users.helper';
describe('LIVECHAT - Agents', function () {
this.retries(0);
@@ -114,6 +114,68 @@ describe('LIVECHAT - Agents', function () {
expect(res.body.users.every((u: { statusLivechat: string }) => u.statusLivechat === 'available')).to.be.true;
});
});
+ it('should return an array of available/unavailable agents when onlyAvailable is false', async () => {
+ await request
+ .get(api('livechat/users/agent'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .query({ onlyAvailable: false })
+ .expect(200)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body.users).to.be.an('array');
+ expect(res.body).to.have.property('offset');
+ expect(res.body).to.have.property('total');
+ expect(res.body).to.have.property('count');
+ expect(
+ res.body.users.every(
+ (u: { statusLivechat: string }) => !u.statusLivechat || ['available', 'not-available'].includes(u.statusLivechat),
+ ),
+ ).to.be.true;
+ });
+ });
+
+ it('should return offline agents when showIdleAgents is true', async () => {
+ await setUserStatus(agent2.credentials, UserStatus.OFFLINE);
+ await request
+ .get(api('livechat/users/agent'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .query({ showIdleAgents: true })
+ .expect(200)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body.users).to.be.an('array');
+ expect(res.body).to.have.property('offset');
+ expect(res.body).to.have.property('total');
+ expect(res.body).to.have.property('count');
+ expect(
+ res.body.users.every(
+ (u: { status: UserStatus }) =>
+ !u.status || [UserStatus.ONLINE, UserStatus.OFFLINE, UserStatus.AWAY, UserStatus.BUSY].includes(u.status),
+ ),
+ ).to.be.true;
+ });
+ });
+
+ it('should return only online agents when showIdleAgents is false', async () => {
+ await setUserStatus(agent2.credentials, UserStatus.ONLINE);
+ await request
+ .get(api('livechat/users/agent'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .query({ showIdleAgents: false })
+ .expect(200)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body.users).to.be.an('array');
+ expect(res.body).to.have.property('offset');
+ expect(res.body).to.have.property('total');
+ expect(res.body).to.have.property('count');
+ expect(res.body.users.every((u: { status: UserStatus }) => u.status !== UserStatus.OFFLINE)).to.be.true;
+ });
+ });
+
it('should return an array of managers', async () => {
await updatePermission('view-livechat-manager', ['admin']);
await updatePermission('manage-livechat-agents', ['admin']);
diff --git a/packages/livechat/src/lib/hooks.js b/packages/livechat/src/lib/hooks.js
index b988579acfa..79535831217 100644
--- a/packages/livechat/src/lib/hooks.js
+++ b/packages/livechat/src/lib/hooks.js
@@ -137,15 +137,11 @@ const api = {
},
async setGuestToken(token) {
- const {
- token: localToken,
- iframe,
- iframe: { guest },
- } = store.state;
+ const { token: localToken } = store.state;
if (token === localToken) {
return;
}
- store.setState({ token, iframe: { ...iframe, guest: { ...guest, token } } });
+ createOrUpdateGuest({ token });
await loadConfig();
},
diff --git a/packages/livechat/src/lib/triggers.js b/packages/livechat/src/lib/triggers.js
index 4cf01090587..efa9e9bdc65 100644
--- a/packages/livechat/src/lib/triggers.js
+++ b/packages/livechat/src/lib/triggers.js
@@ -34,7 +34,7 @@ const getAgent = (triggerAction) => {
let agent;
try {
- agent = await Livechat.nextAgent(department);
+ agent = await Livechat.nextAgent({ department });
} catch (error) {
return reject(error);
}
diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts
index 4f9317fa3d0..c86b87c2b59 100644
--- a/packages/rest-typings/src/v1/omnichannel.ts
+++ b/packages/rest-typings/src/v1/omnichannel.ts
@@ -765,7 +765,7 @@ const LivechatUsersManagerGETSchema = {
nullable: true,
},
onlyAvailable: {
- type: 'string',
+ type: 'boolean',
nullable: true,
},
excludeId: {