Skip to content

Commit a27382f

Browse files
authored
Merge branch 'RocketChat:develop' into matrix-integration
2 parents 935f9c8 + 89e85b7 commit a27382f

22 files changed

Lines changed: 603 additions & 173 deletions

File tree

packages/api/rollup.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import dts from 'rollup-plugin-dts'
22
import esbuild from 'rollup-plugin-esbuild'
3-
import packageJson from './package.json' assert { type: 'json' };
43
import path from 'path';
4+
import { createRequire } from 'module';
5+
import { fileURLToPath } from 'url';
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const require = createRequire(import.meta.url);
10+
const packageJson = require(path.resolve(__dirname, './package.json'));
511

612
const name = packageJson.main.replace(/\.js$/, '');
713

packages/api/src/EmbeddedChatApi.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,10 @@ export default class EmbeddedChatApi implements IChatProvider {
415415

416416
if (suggestedUsername.success) {
417417
const response2 = await fetch(`${this.host}/api/v1/users.update`, {
418-
body: `{"userId": "${userid}", "data": { "username": "${suggestedUsername.result}" }}`,
418+
body: JSON.stringify({
419+
userId: userid,
420+
data: { username: suggestedUsername.result },
421+
}),
419422
headers: {
420423
"Content-Type": "application/json",
421424
"X-Auth-Token": authToken,
@@ -440,7 +443,10 @@ export default class EmbeddedChatApi implements IChatProvider {
440443
try {
441444
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
442445
const response = await fetch(`${this.host}/api/v1/users.update`, {
443-
body: `{"userId": "${userid}", "data": { "username": "${newUserName}" }}`,
446+
body: JSON.stringify({
447+
userId: userid,
448+
data: { username: newUserName },
449+
}),
444450
headers: {
445451
"Content-Type": "application/json",
446452
"X-Auth-Token": authToken,
@@ -633,7 +639,7 @@ export default class EmbeddedChatApi implements IChatProvider {
633639
try {
634640
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
635641
const messages = await fetch(
636-
`${this.host}/api/v1/chat.getThreadMessages?roomId=${this.rid}&tmid=${tmid}`,
642+
`${this.host}/api/v1/chat.getThreadMessages?tmid=${tmid}`,
637643
{
638644
headers: {
639645
"Content-Type": "application/json",
@@ -777,7 +783,7 @@ export default class EmbeddedChatApi implements IChatProvider {
777783
try {
778784
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
779785
const response = await fetch(`${this.host}/api/v1/chat.delete`, {
780-
body: `{"roomId": "${this.rid}", "msgId": "${msgId}"}`,
786+
body: JSON.stringify({ roomId: this.rid, msgId }),
781787
headers: {
782788
"Content-Type": "application/json",
783789
"X-Auth-Token": authToken,
@@ -795,7 +801,7 @@ export default class EmbeddedChatApi implements IChatProvider {
795801
try {
796802
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
797803
const response = await fetch(`${this.host}/api/v1/chat.update`, {
798-
body: `{"roomId": "${this.rid}", "msgId": "${msgId}","text" : "${text}" }`,
804+
body: JSON.stringify({ roomId: this.rid, msgId, text }),
799805
headers: {
800806
"Content-Type": "application/json",
801807
"X-Auth-Token": authToken,
@@ -855,7 +861,7 @@ export default class EmbeddedChatApi implements IChatProvider {
855861
try {
856862
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
857863
const response = await fetch(`${this.host}/api/v1/chat.starMessage`, {
858-
body: `{"messageId": "${mid}"}`,
864+
body: JSON.stringify({ messageId: mid }),
859865
headers: {
860866
"Content-Type": "application/json",
861867
"X-Auth-Token": authToken,
@@ -873,7 +879,7 @@ export default class EmbeddedChatApi implements IChatProvider {
873879
try {
874880
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
875881
const response = await fetch(`${this.host}/api/v1/chat.unStarMessage`, {
876-
body: `{"messageId": "${mid}"}`,
882+
body: JSON.stringify({ messageId: mid }),
877883
headers: {
878884
"Content-Type": "application/json",
879885
"X-Auth-Token": authToken,
@@ -951,7 +957,7 @@ export default class EmbeddedChatApi implements IChatProvider {
951957
try {
952958
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
953959
const response = await fetch(`${this.host}/api/v1/chat.pinMessage`, {
954-
body: `{"messageId": "${mid}"}`,
960+
body: JSON.stringify({ messageId: mid }),
955961
headers: {
956962
"Content-Type": "application/json",
957963
"X-Auth-Token": authToken,
@@ -971,7 +977,7 @@ export default class EmbeddedChatApi implements IChatProvider {
971977
try {
972978
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
973979
const response = await fetch(`${this.host}/api/v1/chat.unPinMessage`, {
974-
body: `{"messageId": "${mid}"}`,
980+
body: JSON.stringify({ messageId: mid }),
975981
headers: {
976982
"Content-Type": "application/json",
977983
"X-Auth-Token": authToken,
@@ -989,7 +995,11 @@ export default class EmbeddedChatApi implements IChatProvider {
989995
try {
990996
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
991997
const response = await fetch(`${this.host}/api/v1/chat.react`, {
992-
body: `{"messageId": "${messageId}", "emoji": "${emoji}", "shouldReact": ${shouldReact}}`,
998+
body: JSON.stringify({
999+
messageId,
1000+
emoji,
1001+
shouldReact,
1002+
}),
9931003
headers: {
9941004
"Content-Type": "application/json",
9951005
"X-Auth-Token": authToken,
@@ -1007,7 +1017,7 @@ export default class EmbeddedChatApi implements IChatProvider {
10071017
try {
10081018
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
10091019
const response = await fetch(`${this.host}/api/v1/chat.reportMessage`, {
1010-
body: `{"messageId": "${messageId}", "description": "${description}"}`,
1020+
body: JSON.stringify({ messageId, description }),
10111021
headers: {
10121022
"Content-Type": "application/json",
10131023
"X-Auth-Token": authToken,
@@ -1192,7 +1202,15 @@ export default class EmbeddedChatApi implements IChatProvider {
11921202
return data;
11931203
}
11941204

1195-
async execCommand({ command, params }: { command: string; params: string }) {
1205+
async execCommand({
1206+
command,
1207+
params,
1208+
tmid,
1209+
}: {
1210+
command: string;
1211+
params: string;
1212+
tmid?: string;
1213+
}) {
11961214
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
11971215
const response = await fetch(`${this.host}/api/v1/commands.run`, {
11981216
headers: {
@@ -1204,6 +1222,7 @@ export default class EmbeddedChatApi implements IChatProvider {
12041222
body: JSON.stringify({
12051223
command,
12061224
params,
1225+
tmid,
12071226
roomId: this.rid,
12081227
triggerId: Math.random().toString(32).slice(2, 20),
12091228
}),

packages/auth/rollup.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import dts from 'rollup-plugin-dts'
22
import esbuild from 'rollup-plugin-esbuild'
33
import path from 'path';
4-
import packageJson from './package.json' assert { type: 'json' };
4+
import { createRequire } from 'module';
5+
import { fileURLToPath } from 'url';
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const require = createRequire(import.meta.url);
10+
const packageJson = require(path.resolve(__dirname, './package.json'));
511

612
const name = packageJson.main.replace(/\.js$/, '');
713

packages/react/src/hooks/useMediaRecorder.js

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useRef } from 'react';
1+
import { useState, useRef, useEffect } from 'react';
22

33
function useUserMedia(constraints, videoRef) {
44
const [stream, setStream] = useState();
@@ -47,3 +47,80 @@ export function useMediaRecorder({ constraints, onStop, videoRef }) {
4747

4848
return [start, stop];
4949
}
50+
51+
export function useNewMediaRecorder({ constraints, videoRef, onStop }) {
52+
const [stream, setStream] = useState();
53+
const [isStreaming, setIsStreaming] = useState(false);
54+
const [recorder, setRecorder] = useState();
55+
const [recordingData, setRecordingData] = useState(null);
56+
const chunks = useRef([]);
57+
58+
async function startCameraAndMic() {
59+
if (isStreaming) return;
60+
try {
61+
const _stream = await navigator.mediaDevices.getUserMedia(constraints);
62+
setStream(_stream);
63+
setIsStreaming(true);
64+
if (videoRef.current) {
65+
videoRef.current.srcObject = _stream;
66+
}
67+
} catch (error) {
68+
console.error('Error starting camera and mic:', error);
69+
}
70+
}
71+
72+
async function startRecording() {
73+
if (!isStreaming) {
74+
console.error('Camera and mic must be on to start recording.');
75+
return;
76+
}
77+
78+
chunks.current = [];
79+
const _recorder = new MediaRecorder(stream);
80+
_recorder.start();
81+
setRecorder(_recorder);
82+
83+
_recorder.addEventListener('dataavailable', (event) => {
84+
chunks.current.push(event.data);
85+
});
86+
87+
_recorder.addEventListener('stop', () => {
88+
setRecordingData(new Blob(chunks.current, { type: 'video/mp4' }));
89+
onStop && onStop(chunks.current);
90+
});
91+
}
92+
93+
async function stopRecording() {
94+
if (recorder && recorder.state === 'recording') {
95+
recorder.stop();
96+
}
97+
}
98+
99+
function getRecording() {
100+
return recordingData;
101+
}
102+
103+
function deleteRecording() {
104+
setRecordingData(null);
105+
chunks.current = [];
106+
}
107+
108+
function stopCameraAndMic() {
109+
if (stream) {
110+
stream.getTracks().forEach((track) => track.stop());
111+
setStream(null);
112+
setIsStreaming(false);
113+
}
114+
}
115+
116+
useEffect(() => () => stopCameraAndMic(), []);
117+
118+
return {
119+
startCameraAndMic,
120+
startRecording,
121+
stopRecording,
122+
getRecording,
123+
deleteRecording,
124+
stopCameraAndMic,
125+
};
126+
}

packages/react/src/views/ChatBody/ChatBody.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const ChatBody = ({
4040
showRoles,
4141
messageListRef,
4242
scrollToBottom,
43+
clearUnreadDividerRef,
4344
}) => {
4445
const { classNames, styleOverrides } = useComponentOverrides('ChatBody');
4546
const { theme, mode } = useTheme();
@@ -49,6 +50,8 @@ const ChatBody = ({
4950
const [, setIsUserScrolledUp] = useState(false);
5051
const [otherUserMessage, setOtherUserMessage] = useState(false);
5152
const [isOverflowing, setIsOverflowing] = useState(false);
53+
const [firstUnreadMessageId, setFirstUnreadMessageId] = useState(null);
54+
const pendingFirstUnreadRef = useRef(null);
5255
const { RCInstance, ECOptions } = useContext(RCContext);
5356
const showAnnouncement = ECOptions?.showAnnouncement;
5457
const messages = useMessageStore((state) => state.messages);
@@ -124,6 +127,10 @@ const ChatBody = ({
124127
const isScrolledUp = messageListRef?.current?.scrollTop !== 0;
125128
if (isScrolledUp && !('pinned' in message) && !('starred' in message)) {
126129
setOtherUserMessage(true);
130+
// Track the first unread message (only set if not already tracking)
131+
if (!pendingFirstUnreadRef.current) {
132+
pendingFirstUnreadRef.current = message._id;
133+
}
127134
}
128135
}
129136
upsertMessage(message, ECOptions?.enableThreads);
@@ -177,7 +184,22 @@ const ChatBody = ({
177184
});
178185
}, []);
179186

187+
// Expose clearUnreadDivider function via ref for ChatInput to call
188+
useEffect(() => {
189+
if (clearUnreadDividerRef) {
190+
clearUnreadDividerRef.current = () => {
191+
setFirstUnreadMessageId(null);
192+
pendingFirstUnreadRef.current = null;
193+
};
194+
}
195+
}, [clearUnreadDividerRef]);
196+
180197
const handlePopupClick = () => {
198+
// Set the unread divider to show above the first unread message
199+
if (pendingFirstUnreadRef.current) {
200+
setFirstUnreadMessageId(pendingFirstUnreadRef.current);
201+
pendingFirstUnreadRef.current = null;
202+
}
181203
scrollToBottom();
182204
setIsUserScrolledUp(false);
183205
setOtherUserMessage(false);
@@ -242,6 +264,12 @@ const ChatBody = ({
242264
setPopupVisible(false);
243265
setIsUserScrolledUp(false);
244266
setOtherUserMessage(false);
267+
// Clear unread divider when scrolled to bottom
268+
if (firstUnreadMessageId) {
269+
setFirstUnreadMessageId(null);
270+
}
271+
// Also clear pending unread ref
272+
pendingFirstUnreadRef.current = null;
245273
}
246274
}, [
247275
messageListRef,
@@ -258,6 +286,7 @@ const ChatBody = ({
258286
setIsUserScrolledUp,
259287
setPopupVisible,
260288
setOtherUserMessage,
289+
firstUnreadMessageId,
261290
]);
262291

263292
const showNewMessagesPopup = () => {
@@ -392,6 +421,7 @@ const ChatBody = ({
392421
loadingOlderMessages={loadingOlderMessages}
393422
isUserAuthenticated={isUserAuthenticated}
394423
hasMoreMessages={hasMoreMessages}
424+
firstUnreadMessageId={firstUnreadMessageId}
395425
/>
396426
)}
397427

packages/react/src/views/ChatHeader/ChatHeader.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,9 @@ const ChatHeader = ({
399399
<div
400400
css={css`
401401
font-size: ${fullScreen ? '1.3rem' : '1.25rem'};
402+
overflow: hidden;
403+
text-overflow: ellipsis;
404+
white-space: nowrap;
402405
`}
403406
>
404407
{channelInfo.name || channelName || 'channelName'}

packages/react/src/views/ChatHeader/ChatHeader.styles.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const getChatHeaderStyles = ({ theme, mode }) => {
1818
padding: 0 0.75rem;
1919
justify-content: space-between;
2020
width: 100%;
21+
flex-wrap: wrap;
22+
gap: 0.5rem;
2123
`,
2224

2325
chatHeaderParent: css`
@@ -42,20 +44,30 @@ const getChatHeaderStyles = ({ theme, mode }) => {
4244
channelDescription: css`
4345
${rowCentreAlign}
4446
flex: 1;
45-
min-width: 0;
47+
min-width: 120px;
4648
gap: 0.5rem;
49+
overflow: hidden;
4750
`,
4851

4952
chatHeaderIconRow: css`
5053
${rowCentreAlign}
51-
position:relative;
54+
position: relative;
5255
gap: 0.5rem;
56+
flex-shrink: 0;
57+
@media (max-width: 380px) {
58+
gap: 0.25rem;
59+
flex-wrap: wrap;
60+
justify-content: flex-end;
61+
}
5362
`,
5463
channelName: css`
5564
display: flex;
5665
align-items: center;
5766
gap: 0.1rem;
5867
cursor: pointer;
68+
overflow: hidden;
69+
text-overflow: ellipsis;
70+
white-space: nowrap;
5971
`,
6072
channelTopic: css`
6173
opacity: 0.8rem;

0 commit comments

Comments
 (0)