Skip to content

Commit 26550bd

Browse files
Merge branch 'develop' into nonstop-sound
2 parents 4fe5d1f + 209ea9f commit 26550bd

File tree

83 files changed

+853
-471
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+853
-471
lines changed

.changeset/forty-ghosts-flow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
---
4+
5+
Fixed "Take it" button behavior disabling it when agent status is set to offline

.changeset/late-drinks-brake.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": minor
3+
---
4+
5+
Fixed an issue that would not allow the user to dismiss the closeToSeatsLimit banner for old workspaces
6+

.changeset/wild-carrots-know.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
---
4+
5+
Remove password change reason when the `request password change` option is set to false

apps/meteor/app/lib/server/functions/saveUser.js

+4
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ export const saveUser = async function (userId, userData) {
401401

402402
const updateUser = {
403403
$set: {},
404+
$unset: {},
404405
};
405406

406407
handleBio(updateUser, userData.bio);
@@ -419,6 +420,9 @@ export const saveUser = async function (userId, userData) {
419420

420421
if (typeof userData.requirePasswordChange !== 'undefined') {
421422
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
423+
if (!userData.requirePasswordChange) {
424+
updateUser.$unset.requirePasswordChangeReason = 1;
425+
}
422426
}
423427

424428
if (typeof userData.verified === 'boolean') {

apps/meteor/app/otr/client/OTR.ts

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ class OTR implements IOTR {
2424
this.instancesByRoomId[rid] = otrRoom;
2525
return this.instancesByRoomId[rid];
2626
}
27+
28+
closeAllInstances(): void {
29+
// Resets state, but doesnt emit events
30+
// Other party should receive event and fire events
31+
Object.values(this.instancesByRoomId).forEach((instance) => {
32+
instance.softReset();
33+
});
34+
35+
this.instancesByRoomId = {};
36+
}
2737
}
2838

2939
export default new OTR();

apps/meteor/app/otr/client/OTRRoom.ts

+45
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { IRoom, IMessage, IUser } from '@rocket.chat/core-typings';
2+
import { UserStatus } from '@rocket.chat/core-typings';
23
import { Random } from '@rocket.chat/random';
34
import EJSON from 'ejson';
45
import { Meteor } from 'meteor/meteor';
@@ -7,6 +8,7 @@ import { Tracker } from 'meteor/tracker';
78

89
import GenericModal from '../../../client/components/GenericModal';
910
import { imperativeModal } from '../../../client/lib/imperativeModal';
11+
import type { UserPresence } from '../../../client/lib/presence';
1012
import { Presence } from '../../../client/lib/presence';
1113
import { dispatchToastMessage } from '../../../client/lib/toast';
1214
import { getUidDirectMessage } from '../../../client/lib/utils/getUidDirectMessage';
@@ -47,13 +49,16 @@ export class OTRRoom implements IOTRRoom {
4749

4850
private isFirstOTR: boolean;
4951

52+
private onPresenceEventHook: (event: UserPresence | undefined) => void;
53+
5054
protected constructor(uid: IUser['_id'], rid: IRoom['_id'], peerId: IUser['_id']) {
5155
this._userId = uid;
5256
this._roomId = rid;
5357
this._keyPair = null;
5458
this._sessionKey = null;
5559
this.peerId = peerId;
5660
this.isFirstOTR = true;
61+
this.onPresenceEventHook = this.onPresenceEvent.bind(this);
5762
}
5863

5964
public static create(uid: IUser['_id'], rid: IRoom['_id']): OTRRoom | undefined {
@@ -110,6 +115,35 @@ export class OTRRoom implements IOTRRoom {
110115
}
111116
}
112117

118+
onPresenceEvent(event: UserPresence | undefined): void {
119+
if (!event) {
120+
return;
121+
}
122+
if (event.status !== UserStatus.OFFLINE) {
123+
return;
124+
}
125+
console.warn(`OTR Room ${this._roomId} ended because ${this.peerId} went offline`);
126+
this.end();
127+
128+
imperativeModal.open({
129+
component: GenericModal,
130+
props: {
131+
variant: 'warning',
132+
title: t('OTR'),
133+
children: t('OTR_Session_ended_other_user_went_offline', { username: event.username }),
134+
confirmText: t('Ok'),
135+
onClose: imperativeModal.close,
136+
onConfirm: imperativeModal.close,
137+
},
138+
});
139+
}
140+
141+
// Starts listening to other user's status changes and end OTR if any of the Users goes offline
142+
// this should be called in 2 places: on acknowledge (meaning user accepted OTR) or on establish (meaning user initiated OTR)
143+
listenToUserStatus(): void {
144+
Presence.listen(this.peerId, this.onPresenceEventHook);
145+
}
146+
113147
acknowledge(): void {
114148
void sdk.rest.post('/v1/statistics.telemetry', { params: [{ eventName: 'otrStats', timestamp: Date.now(), rid: this._roomId }] });
115149

@@ -137,10 +171,19 @@ export class OTRRoom implements IOTRRoom {
137171
]);
138172
}
139173

174+
softReset(): void {
175+
this.isFirstOTR = true;
176+
this.setState(OtrRoomState.NOT_STARTED);
177+
this._keyPair = null;
178+
this._exportedPublicKey = {};
179+
this._sessionKey = null;
180+
}
181+
140182
end(): void {
141183
this.isFirstOTR = true;
142184
this.reset();
143185
this.setState(OtrRoomState.NOT_STARTED);
186+
Presence.stop(this.peerId, this.onPresenceEventHook);
144187
sdk.publish('notify-user', [
145188
`${this.peerId}/otr`,
146189
'end',
@@ -285,6 +328,7 @@ export class OTRRoom implements IOTRRoom {
285328
setTimeout(async () => {
286329
this.setState(OtrRoomState.ESTABLISHED);
287330
this.acknowledge();
331+
this.listenToUserStatus();
288332

289333
if (data.refresh) {
290334
await sdk.rest.post('/v1/chat.otr', {
@@ -362,6 +406,7 @@ export class OTRRoom implements IOTRRoom {
362406
this.setState(OtrRoomState.ESTABLISHED);
363407

364408
if (this.isFirstOTR) {
409+
this.listenToUserStatus();
365410
await sdk.rest.post('/v1/chat.otr', {
366411
roomId: this._roomId,
367412
type: otrSystemMessages.USER_JOINED_OTR,

apps/meteor/app/otr/client/events.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Accounts } from 'meteor/accounts-base';
2+
3+
import OTR from './OTR';
4+
5+
Accounts.onLogout(() => {
6+
OTR.closeAllInstances();
7+
});

apps/meteor/app/otr/client/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import './OTRRoom';
22
import './OTR';
33
import './messageTypes';
4+
import './events';

apps/meteor/client/components/ImageGallery/ImageGallery.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,19 @@ export const ImageGallery = ({ images, onClose, loadMore }: { images: IUpload[];
176176
modules={[Navigation, Zoom, Keyboard, A11y]}
177177
onInit={(swiper) => setSwiperInst(swiper)}
178178
onSlidesGridLengthChange={(swiper) => {
179-
swiper.slideTo(images.length - gridSize, 2000);
179+
swiper.slideTo(images.length - gridSize, 0);
180180
setGridSize(images.length);
181181
}}
182182
onReachBeginning={loadMore}
183183
initialSlide={images.length - 1}
184184
>
185-
{images.toReversed().map(({ _id, url }) => (
185+
{[...images].reverse().map(({ _id, url }) => (
186186
<SwiperSlide key={_id}>
187187
<div className='swiper-zoom-container'>
188188
{/* eslint-disable-next-line
189189
jsx-a11y/no-noninteractive-element-interactions,
190-
jsx-a11y/click-events-have-key-events
191-
*/}
190+
jsx-a11y/click-events-have-key-events
191+
*/}
192192
<img src={url} loading='lazy' alt='' data-qa-zoom-scale={zoomScale} onClick={preventPropagation} />
193193
<div className='rcx-lazy-preloader'>
194194
<Throbber inheritColor />

apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannelInquiry.tsx

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { MessageFooterCallout, MessageFooterCalloutAction, MessageFooterCalloutContent } from '@rocket.chat/ui-composer';
2-
import { useEndpoint, useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
2+
import { useEndpoint, useMethod, useToastMessageDispatch, useTranslation, useUser } from '@rocket.chat/ui-contexts';
33
import { useQuery } from '@tanstack/react-query';
44
import type { ReactElement } from 'react';
5-
import React from 'react';
5+
import React, { useMemo } from 'react';
66

7+
import { useOmnichannelAgentAvailable } from '../../../../hooks/omnichannel/useOmnichannelAgentAvailable';
78
import { useOmnichannelRoom } from '../../contexts/RoomContext';
89

910
export const ComposerOmnichannelInquiry = (): ReactElement => {
11+
const t = useTranslation();
1012
const dispatchToastMessage = useToastMessageDispatch();
11-
13+
const user = useUser();
14+
const agentAvailable = useOmnichannelAgentAvailable();
1215
const room = useOmnichannelRoom();
1316
const getInquire = useEndpoint('GET', `/v1/livechat/inquiries.getOne`);
1417
const result = useQuery(['inquire', room._id], () =>
@@ -33,11 +36,24 @@ export const ComposerOmnichannelInquiry = (): ReactElement => {
3336
}
3437
};
3538

36-
const t = useTranslation();
39+
const title = useMemo(() => {
40+
if (user?.status === 'offline') {
41+
return t('You_cant_take_chats_offline');
42+
}
43+
44+
if (!agentAvailable) {
45+
return t('You_cant_take_chats_unavailable');
46+
}
47+
}, [agentAvailable, t, user?.status]);
48+
3749
return (
3850
<MessageFooterCallout aria-busy={result.isLoading}>
3951
<MessageFooterCalloutContent>{t('you_are_in_preview_mode_of_incoming_livechat')}</MessageFooterCalloutContent>
40-
<MessageFooterCalloutAction disabled={result.isLoading} onClick={handleTakeInquiry}>
52+
<MessageFooterCalloutAction
53+
{...(title && { title })}
54+
disabled={result.isLoading || user?.status === 'offline' || !agentAvailable}
55+
onClick={handleTakeInquiry}
56+
>
4157
{t('Take_it')}
4258
</MessageFooterCalloutAction>
4359
</MessageFooterCallout>

apps/meteor/server/services/banner/service.ts

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ export class BannerService extends ServiceClassInternal implements IBannerServic
6868
.filter((banner) => !dismissed.has(banner._id))
6969
.map((banner) => ({
7070
...banner,
71+
view: {
72+
...banner.view,
73+
// All modern banners should have a viewId, but we have old banners that were created without it
74+
// such as the seatsTaken banner. In this case, we use the bannerId as the viewId
75+
viewId: banner.view.viewId || banner._id,
76+
},
7177
// add surface to legacy banners
7278
surface: !banner.surface ? 'banner' : banner.surface,
7379
}));

apps/meteor/tests/data/users.helper.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { UserStatus } from '@rocket.chat/core-typings';
22
import { api, credentials, request } from './api-data';
33
import { password } from './user';
4+
import { MongoClient } from 'mongodb';
5+
import { URL_MONGODB } from '../e2e/config/constants';
46

57
export const createUser = (userData = {}) =>
68
new Promise((resolve) => {
@@ -105,3 +107,15 @@ export const registerUser = async (userData = {}, overrideCredentials = credenti
105107

106108
return result.body.user;
107109
};
110+
111+
// For changing user data when it's not possible to do so via API
112+
export const updateUserInDb = async (userId, userData) => {
113+
const connection = await MongoClient.connect(URL_MONGODB);
114+
115+
await connection
116+
.db()
117+
.collection('users')
118+
.updateOne({ _id: userId }, { $set: { ...userData } });
119+
120+
await connection.close();
121+
};

apps/meteor/tests/e2e/.eslintrc.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"root": true,
3-
"extends": ["@rocket.chat/eslint-config/original", "@rocket.chat/eslint-config/react", "prettier", "plugin:@typescript-eslint/recommended"],
3+
"extends": ["@rocket.chat/eslint-config", "@rocket.chat/eslint-config/react", "prettier", "plugin:@typescript-eslint/recommended"],
44
"parser": "@typescript-eslint/parser",
55
"plugins": ["prettier", "testing-library", "anti-trojan-source", "no-floating-promise"],
66
"rules": {

apps/meteor/tests/e2e/access-security-page.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test.describe.serial('access-security-page', () => {
2727
setSettingValueById(api, 'Accounts_AllowPasswordChange', true),
2828
setSettingValueById(api, 'Accounts_TwoFactorAuthentication_Enabled', true),
2929
setSettingValueById(api, 'E2E_Enable', false),
30-
])
30+
]),
3131
);
3232

3333
test('security tab is invisible when password change, 2FA and E2E are disabled', async ({ page }) => {
@@ -77,5 +77,5 @@ test.describe.serial('access-security-page', () => {
7777
]);
7878
await expect(poAccountProfile.securityE2EEncryptionSection).toBeVisible();
7979
});
80-
})
80+
});
8181
});

0 commit comments

Comments
 (0)