Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useGoToDirectMessage } from '@rocket.chat/ui-client';
import { useRouter, useUserAvatarPath } from '@rocket.chat/ui-contexts';
import { useMediaCallContext } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

import { useDirectMessageAction } from '../room/hooks/useUserInfoActions/actions/useDirectMessageAction';

export type InternalCallHistoryContact = {
_id: string;
name?: string;
Expand Down Expand Up @@ -48,21 +47,7 @@ export const useMediaCallInternalHistoryActions = ({
});
});

const directMessage = useDirectMessageAction(contact, openRoomId ?? '');

const goToDirectMessage = useMemo(() => {
if (directMessage?.onClick) {
return directMessage.onClick;
}
if (!messageRoomId || openRoomId) {
return;
}
return () =>
router.navigate({
name: 'direct',
params: { rid: messageRoomId },
});
}, [directMessage?.onClick, messageRoomId, openRoomId, router]);
const goToDirectMessage = useGoToDirectMessage({ username: contact.username }, openRoomId ?? '');

const jumpToMessage = useEffectEvent(() => {
const rid = messageRoomId || openRoomId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,27 @@
import type { IRoom, IUser, ISubscription } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useTranslation, usePermission, useRoute, useUserSubscription, useUserSubscriptionByName } from '@rocket.chat/ui-contexts';
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { useGoToDirectMessage } from '@rocket.chat/ui-client';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import type { UserInfoAction, UserInfoActionType } from '../useUserInfoActions';

const getShouldOpenDirectMessage = (
currentSubscription?: ISubscription,
usernameSubscription?: ISubscription,
canOpenDirectMessage?: boolean,
username?: IUser['username'],
): boolean => {
const canOpenDm = canOpenDirectMessage || usernameSubscription;
const directMessageIsNotAlreadyOpen = currentSubscription && currentSubscription.name !== username;
return (canOpenDm && directMessageIsNotAlreadyOpen) ?? false;
};
import type { UserInfoAction } from '../useUserInfoActions';

export const useDirectMessageAction = (user: Pick<IUser, '_id' | 'username'>, rid: IRoom['_id']): UserInfoAction | undefined => {
const t = useTranslation();
const usernameSubscription = useUserSubscriptionByName(user.username ?? '');
const currentSubscription = useUserSubscription(rid);
const canOpenDirectMessage = usePermission('create-d');
const directRoute = useRoute('direct');
const { t } = useTranslation();

const shouldOpenDirectMessage = getShouldOpenDirectMessage(
currentSubscription,
usernameSubscription,
canOpenDirectMessage,
user.username,
);
const openDirectMessage = useGoToDirectMessage(user, rid);

const openDirectMessage = useEffectEvent(
() =>
user.username &&
directRoute.push({
rid: user.username,
}),
);
const openDirectMessageOption = useMemo(() => {
if (!openDirectMessage) {
return undefined;
}

const openDirectMessageOption = useMemo(
() =>
shouldOpenDirectMessage
? {
content: t('Direct_Message'),
icon: 'balloon' as const,
onClick: openDirectMessage,
type: 'communication' as UserInfoActionType,
}
: undefined,
[openDirectMessage, shouldOpenDirectMessage, t],
);
return {
content: t('Direct_Message'),
icon: 'balloon' as const,
onClick: openDirectMessage,
type: 'communication',
} as const;
}, [openDirectMessage, t]);

return openDirectMessageOption;
};
29 changes: 25 additions & 4 deletions packages/media-signaling/src/lib/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,20 @@ export class MediaSignalingSession extends Emitter<MediaSignalingEvents> {
return this.hangupCallsThatNeedInput();
}

return this.setInputTrack(tracks[0]);
const inputTrack = tracks[0];

// If we no longer have a call that can use this track, just release it
if (inputTrack && !this.mayNeedInputTrack()) {
try {
// Stop the track so the browser doesn't have to wait for GC to detect that the stream is not in use
inputTrack.stop();
} catch {
// we don't care if this failed
}
return;
}

return this.setInputTrack(inputTrack);
}

private hangupCallsThatNeedInput(): void {
Expand All @@ -445,14 +458,22 @@ export class MediaSignalingSession extends Emitter<MediaSignalingEvents> {
}
}

private async maybeStopInputTrack(): Promise<void> {
this.config.logger?.debug('MediaSignalingSession.maybeStopInputTrack');
private mayNeedInputTrack(): boolean {
for (const call of this.knownCalls.values()) {
if (call.mayNeedInputTrack()) {
return;
return true;
}
}

return false;
}

private async maybeStopInputTrack(): Promise<void> {
this.config.logger?.debug('MediaSignalingSession.maybeStopInputTrack');
if (this.mayNeedInputTrack()) {
return;
}

await this.setInputTrack(null);
}

Expand Down
1 change: 1 addition & 0 deletions packages/ui-client/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './useLicense';
export * from './usePreferenceFeaturePreviewList';
export * from './useUserDisplayName';
export * from './useValidatePassword';
export * from './useGoToDirectMessage';
49 changes: 49 additions & 0 deletions packages/ui-client/src/hooks/useGoToDirectMessage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { renderHook } from '@testing-library/react';

import { useGoToDirectMessage } from './useGoToDirectMessage';

it('should return undefined if username is not provided', () => {
const { result } = renderHook(() => useGoToDirectMessage({}), {
wrapper: mockAppRoot().build(),
});

expect(result.current).toBe(undefined);
});

it("should return undefined if the user doesn't have permission to create direct messages and doesn't have a subscription with target user", () => {
const { result } = renderHook(() => useGoToDirectMessage({ username: 'test' }), {
wrapper: mockAppRoot().build(),
});

expect(result.current).toBe(undefined);
});

it('should return undefined if the room is already open', () => {
const { result } = renderHook(() => useGoToDirectMessage({ username: 'test' }, 'test-room'), {
wrapper: mockAppRoot()
.withSubscription({ _id: 'test-room', name: 'test', t: 'd', rid: 'test-room' } as SubscriptionWithRoom)
.build(),
});

expect(result.current).toBe(undefined);
});

it('should return a function to navigate to the direct message room if the user has permission to create direct messages and no subscription with target user', () => {
const { result } = renderHook(() => useGoToDirectMessage({ username: 'test' }), {
wrapper: mockAppRoot().withPermission('create-d').build(),
});

expect(typeof result.current).toBe('function');
});

it("should return a function to navigate to the direct message room if the user has a subscription with target user and doesn't have permission to create direct messages", () => {
const { result } = renderHook(() => useGoToDirectMessage({ username: 'test' }), {
wrapper: mockAppRoot()
.withSubscription({ _id: 'test-room', name: 'test', t: 'd', rid: 'test-room' } as SubscriptionWithRoom)
.build(),
});

expect(typeof result.current).toBe('function');
});
39 changes: 39 additions & 0 deletions packages/ui-client/src/hooks/useGoToDirectMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { usePermission, useUserSubscriptionByName, useRouter } from '@rocket.chat/ui-contexts';

// TODO: Routes type definitions are declared in-file for most places, so this route doesn't exist in this package
declare module '@rocket.chat/ui-contexts' {
export interface IRouterPaths {
direct: {
pathname: `/direct/:rid${`/${string}` | ''}${`/${string}` | ''}`;
pattern: '/direct/:rid/:tab?/:context?';
};
}
}

/**
* Hook to navigate to a direct message room
* @param targetUser - Object containing the username of the user to navigate to
* @param openRoomId - Optional ID of the room that is already open
* @returns A function to navigate to the direct message room, or undefined if the room is already open or the user doesn't have permission to create direct messages and doesn't have a subscription to the target user
*/
export const useGoToDirectMessage = (targetUser: { username?: string }, openRoomId?: string): (() => void) | undefined => {
const usernameSubscription = useUserSubscriptionByName(targetUser.username ?? '');
const router = useRouter();
const canOpenDirectMessage = usePermission('create-d');

const hasPermissionOrSubscription = usernameSubscription || canOpenDirectMessage;
const alreadyOpen = openRoomId && usernameSubscription?.rid === openRoomId;
const shouldOpen = targetUser.username && hasPermissionOrSubscription && !alreadyOpen;

const openDirectMessage = useEffectEvent(
() =>
targetUser.username &&
router.navigate({
name: 'direct' as const,
params: { rid: targetUser.username },
} as const),
);

return shouldOpen ? openDirectMessage : undefined;
};
Loading