chore: ddp streamer typings (#28437)

Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
pull/28455/head^2
Guilherme Gazzo 3 years ago committed by GitHub
parent 276d012e7c
commit ffa1d9c48c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      apps/meteor/client/hooks/lists/useStreamUpdatesForMessageList.ts
  2. 8
      apps/meteor/client/providers/CallProvider/CallProvider.tsx
  3. 39
      apps/meteor/client/providers/ServerProvider.tsx
  4. 1
      apps/meteor/client/views/admin/import/ImportProgressPage.tsx
  5. 10
      apps/meteor/client/views/banners/hooks/useRemoteBanners.ts
  6. 24
      apps/meteor/client/views/room/contextualBar/Threads/hooks/useThreadMainMessageQuery.ts
  7. 71
      packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/hooks/useVideoConfDataStream.ts
  8. 8
      packages/ui-contexts/src/ServerContext/ServerContext.ts
  9. 2
      packages/ui-contexts/src/ServerContext/methods.ts
  10. 164
      packages/ui-contexts/src/ServerContext/streams.ts
  11. 47
      packages/ui-contexts/src/hooks/useStream.ts
  12. 3
      packages/ui-contexts/src/index.ts

@ -53,13 +53,10 @@ export const useStreamUpdatesForMessageList = (messageList: MessageList, uid: IU
messageList.remove(mid);
});
const unsubscribeFromDeleteMessageBulk = subscribeToNotifyRoom(
`${rid}/deleteMessageBulk`,
(params: NotifyRoomRidDeleteMessageBulkEvent) => {
const matchDeleteCriteria = createDeleteCriteria(params);
messageList.prune(matchDeleteCriteria);
},
);
const unsubscribeFromDeleteMessageBulk = subscribeToNotifyRoom(`${rid}/deleteMessageBulk`, (params) => {
const matchDeleteCriteria = createDeleteCriteria(params);
messageList.prune(matchDeleteCriteria);
});
return (): void => {
unsubscribeFromRoomMessages();

@ -271,7 +271,7 @@ export const CallProvider: FC = ({ children }) => {
return;
}
const handleCallHangup = (_event: { roomId: string }): void => {
return subscribeToNotifyUser(`${user._id}/call.hangup`, (event): void => {
setQueueName(queueAggregator.getCurrentQueueName());
if (hasVoIPEnterpriseLicense) {
@ -281,10 +281,8 @@ export const CallProvider: FC = ({ children }) => {
closeRoom();
dispatchEvent({ event: VoipClientEvents['VOIP-CALL-ENDED'], rid: _event.roomId });
};
return subscribeToNotifyUser(`${user._id}/call.hangup`, handleCallHangup);
dispatchEvent({ event: VoipClientEvents['VOIP-CALL-ENDED'], rid: event.roomId });
});
}, [openWrapUpModal, queueAggregator, subscribeToNotifyUser, user, voipEnabled, dispatchEvent, hasVoIPEnterpriseLicense, closeRoom]);
useEffect(() => {

@ -1,4 +1,5 @@
import type { Serialized } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import type { Method, PathFor, OperationParams, OperationResult, UrlParams, PathPattern } from '@rocket.chat/rest-typings';
import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn, UploadResult } from '@rocket.chat/ui-contexts';
import { ServerContext } from '@rocket.chat/ui-contexts';
@ -68,6 +69,43 @@ const getStream = (
};
};
const ee = new Emitter<Record<string, void>>();
const events = new Map<string, () => void>();
const getSingleStream = (
streamName: string,
): (<TEvent extends unknown[]>(eventName: string, callback: (...event: TEvent) => void) => () => void) => {
const stream = getStream(streamName);
return (eventName, callback): (() => void) => {
ee.on(`${streamName}/${eventName}`, callback);
const handler = (...args: any[]): void => {
ee.emit(`${streamName}/${eventName}`, ...args);
};
const stop = (): void => {
// If someone is still listening, don't unsubscribe
ee.off(`${streamName}/${eventName}`, callback);
if (ee.has(`${streamName}/${eventName}`)) {
return;
}
const unsubscribe = events.get(`${streamName}/${eventName}`);
if (unsubscribe) {
unsubscribe();
events.delete(`${streamName}/${eventName}`);
}
};
if (!events.has(`${streamName}/${eventName}`)) {
events.set(`${streamName}/${eventName}`, stream(eventName, handler));
}
return stop;
};
};
const contextValue = {
info,
absoluteUrl,
@ -75,6 +113,7 @@ const contextValue = {
callEndpoint,
uploadToEndpoint,
getStream,
getSingleStream,
};
const ServerProvider: FC = ({ children }) => <ServerContext.Provider children={children} value={contextValue} />;

@ -135,7 +135,6 @@ const ImportProgressPage = function ImportProgressPage() {
useEffect(() => {
return streamer('progress', ({ count: { completed, total }, ...rest }) => {
console.log('streamer', rest, completed, total);
handleProgressUpdated({ ...rest, completed, total } as any);
});
}, [handleProgressUpdated, streamer]);

@ -40,7 +40,9 @@ export const useRemoteBanners = () => {
});
};
const handleBannerChange = async (event: { bannerId: string }): Promise<void> => {
fetchInitialBanners();
const unsubscribeFromBannerChanged = subscribeToNotifyLoggedIn('banner-changed', async (event): Promise<void> => {
const response = await serverContext.callEndpoint({
method: 'GET',
pathPattern: '/v1/banners/:id',
@ -59,11 +61,7 @@ export const useRemoteBanners = () => {
response.banners.forEach((banner) => {
banners.open(mapBanner(banner));
});
};
fetchInitialBanners();
const unsubscribeFromBannerChanged = subscribeToNotifyLoggedIn('banner-changed', handleBannerChange);
});
return () => {
controller.abort();

@ -13,8 +13,6 @@ import { useGetMessageByID } from './useGetMessageByID';
type RoomMessagesRidEvent = IMessage;
type NotifyRoomRidDeleteMessageEvent = { _id: IMessage['_id'] };
type NotifyRoomRidDeleteMessageBulkEvent = {
rid: IMessage['rid'];
excludePinned: boolean;
@ -50,20 +48,14 @@ const useSubscribeToMessage = () => {
if (message._id === event._id) onMutate?.(event);
});
const unsubscribeFromDeleteMessage = subscribeToNotifyRoom(
`${message.rid}/deleteMessage`,
(event: NotifyRoomRidDeleteMessageEvent) => {
if (message._id === event._id) onDelete?.();
},
);
const unsubscribeFromDeleteMessageBulk = subscribeToNotifyRoom(
`${message.rid}/deleteMessageBulk`,
(params: NotifyRoomRidDeleteMessageBulkEvent) => {
const matchDeleteCriteria = createDeleteCriteria(params);
if (matchDeleteCriteria(message)) onDelete?.();
},
);
const unsubscribeFromDeleteMessage = subscribeToNotifyRoom(`${message.rid}/deleteMessage`, (event) => {
if (message._id === event._id) onDelete?.();
});
const unsubscribeFromDeleteMessageBulk = subscribeToNotifyRoom(`${message.rid}/deleteMessageBulk`, (params) => {
const matchDeleteCriteria = createDeleteCriteria(params);
if (matchDeleteCriteria(message)) onDelete?.();
});
return () => {
unsubscribeFromRoomMessages();

@ -1,57 +1,9 @@
import { useStream } from '@rocket.chat/ui-contexts';
import { Emitter } from '@rocket.chat/emitter';
import { useCallback, useEffect } from 'react';
import { useSingleStream } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useVideoConfData } from './useVideoConfData';
const ee = new Emitter<Record<string, void>>();
const events = new Map<string, () => void>();
const useStreamBySubPath = (
streamer: ReturnType<typeof useStream>,
subpath: string,
callback: () => void
) => {
const maybeSubscribe = useCallback(() => {
// If we're already subscribed, don't do it again
if (events.has(subpath)) {
return;
}
events.set(
subpath,
streamer(subpath, () => {
ee.emit(subpath);
})
);
}, [streamer, subpath]);
const maybeUnsubscribe = useCallback(() => {
// If someone is still listening, don't unsubscribe
if (ee.has(subpath)) {
return;
}
const unsubscribe = events.get(subpath);
if (unsubscribe) {
unsubscribe();
events.delete(subpath);
}
}, [subpath]);
useEffect(() => {
maybeSubscribe();
ee.on(subpath, callback);
return () => {
ee.off(subpath, callback);
maybeUnsubscribe();
};
}, [callback, subpath]);
};
export const useVideoConfDataStream = ({
rid,
callId,
@ -60,16 +12,17 @@ export const useVideoConfDataStream = ({
callId: string;
}) => {
const queryClient = useQueryClient();
const subpath = `${rid}/${callId}`;
const subscribeNotifyRoom = useStream('notify-room');
const subscribeNotifyRoom = useSingleStream('notify-room');
useEffect(() => {
return subscribeNotifyRoom(
`${rid}/videoconf`,
(id) =>
id === callId &&
queryClient.invalidateQueries(['video-conference', callId])
);
}, [rid, callId]);
useStreamBySubPath(
subscribeNotifyRoom,
subpath,
useCallback(() => {
queryClient.invalidateQueries(['video-conference', callId]);
}, [subpath])
);
return useVideoConfData({ callId });
};

@ -38,6 +38,13 @@ export type ServerContextValue = {
retransmitToSelf?: boolean | undefined;
},
) => <TEvent extends unknown[]>(eventName: string, callback: (...event: TEvent) => void) => () => void;
getSingleStream: (
streamName: string,
options?: {
retransmit?: boolean | undefined;
retransmitToSelf?: boolean | undefined;
},
) => <TEvent extends unknown[]>(eventName: string, callback: (...event: TEvent) => void) => () => void;
};
export const ServerContext = createContext<ServerContextValue>({
@ -50,4 +57,5 @@ export const ServerContext = createContext<ServerContextValue>({
throw new Error('not implemented');
},
getStream: () => () => (): void => undefined,
getSingleStream: () => () => (): void => undefined,
});

@ -1,7 +1,5 @@
import type { IMessage, IRoom, IMessageSearchProvider, IMessageSearchSuggestion, IUser } from '@rocket.chat/core-typings';
// TODO: frontend chapter day - define methods
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface ServerMethods {
'addOAuthService': (...args: any[]) => any;

@ -0,0 +1,164 @@
import type {
IMessage,
IRoom,
ISetting,
ISubscription,
IRole,
IEmoji,
ICustomSound,
INotificationDesktop,
IWebdavAccount,
VoipEventDataSignature,
IUser,
IOmnichannelRoom,
VideoConference,
} from '@rocket.chat/core-typings';
type StreamerKeyArgs<K, T extends unknown[]> = (key: K, cb: (...args: T) => void) => () => void;
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface StreamerEvents {
'roles': StreamerKeyArgs<'roles', [IRole]>;
'notify-room': StreamerKeyArgs<`${string}/user-activity`, [username: string, activities: string]> &
StreamerKeyArgs<
`${string}/deleteMessageBulk`,
[args: { rid: IMessage['rid']; excludePinned: boolean; ignoreDiscussion: boolean; ts: Record<string, Date>; users: string[] }]
> &
StreamerKeyArgs<`${string}/deleteMessage`, [{ _id: IMessage['_id'] }]> &
StreamerKeyArgs<`${string}/videoconf`, [id: string]>;
'room-messages': StreamerKeyArgs<string, [IMessage]>;
'notify-all': StreamerKeyArgs<
'deleteEmojiCustom',
[
{
emojiData: IEmoji;
},
]
> &
StreamerKeyArgs<
'updateCustomSound',
[
{
soundData: ICustomSound;
},
]
> &
StreamerKeyArgs<'public-settings-changed', ['inserted' | 'updated' | 'removed' | 'changed', ISetting]>;
'notify-user': StreamerKeyArgs<`${string}/rooms-changed`, [IRoom]> &
StreamerKeyArgs<`${string}/subscriptions-changed`, [ISubscription]> &
StreamerKeyArgs<`${string}/message`, [IMessage]> &
StreamerKeyArgs<`${string}/force_logout`, []> &
StreamerKeyArgs<
`${string}/webdav`,
[
| {
type: 'changed';
account: Partial<IWebdavAccount>;
}
| {
type: 'removed';
account: { _id: IWebdavAccount['_id'] };
},
]
> &
StreamerKeyArgs<`${string}/e2ekeyRequest`, [string, string]> &
StreamerKeyArgs<`${string}/notification`, [INotificationDesktop]> &
StreamerKeyArgs<`${string}/voip.events`, [VoipEventDataSignature]> &
StreamerKeyArgs<
`${string}/call.hangup`,
[
{
roomId: string;
},
]
>;
'importers': StreamerKeyArgs<'progress', [{ rate: number; count: { completed: number; total: number } }]>;
'notify-logged': StreamerKeyArgs<'banner-changed', [{ bannerId: string }]> &
StreamerKeyArgs<
'roles-change',
[
{
type: 'added' | 'removed' | 'changed';
_id: IRole['_id'];
u: {
_id: IUser['_id'];
username: IUser['username'];
name: IUser['name'];
};
scope?: IRoom['_id'];
},
]
> &
StreamerKeyArgs<'Users:NameChanged', [Pick<IUser, '_id' | 'name'>]> &
StreamerKeyArgs<'voip.statuschanged', [boolean]> &
StreamerKeyArgs<'omnichannel.priority-changed', [{ id: 'added' | 'removed' | 'changed'; name: string }]>;
'stdout': StreamerKeyArgs<
'stdout',
[
{
id: string;
string: string;
ts: Date;
},
]
>;
'room-data': StreamerKeyArgs<string, [IOmnichannelRoom]>;
'notify-room-users': StreamerKeyArgs<
`${string}/video-conference`,
[
{
action: string;
params: {
callId: VideoConference['_id'];
uid: IUser['_id'];
rid: IRoom['_id'];
};
},
]
> &
StreamerKeyArgs<`${string}/webrtc`, unknown[]> &
StreamerKeyArgs<`${string}/otr`, unknown[]> &
StreamerKeyArgs<`${string}/userData`, unknown[]>;
// 'notify-logged': (
// e:
// | 'private-settings-changed'
// | 'permissions-changed'
// | 'roles-change'
// | 'deleteCustomUserStatus'
// | 'updateCustomUserStatus'
// | 'deleteEmojiCustom'
// | 'updateEmojiCustom'
// | 'updateAvatar'
// | 'Users:Deleted',
// ) => [void];
// 'apps': (
// e:
// | 'app/added'
// | 'app/removed'
// | 'app/updated'
// | 'app/statusUpdate'
// | 'app/settingUpdate'
// | 'command/added'
// | 'command/disabled'
// | 'command/updated'
// | 'command/removed'
// | 'actions/changed',
// ) => [unknown];
// 'user-presence': () => [void];
}
export type ServerStreamerNames = keyof StreamerEvents;
export type ServerStreamerParameters<MethodName extends ServerStreamerNames> = Parameters<StreamerEvents[MethodName]>;
export type ServerStreamerReturn<MethodName extends ServerStreamerNames> = ReturnType<StreamerEvents[MethodName]>;

@ -1,14 +1,53 @@
import type { ServerStreamerNames, StreamerEvents } from '@rocket.chat/ui-contexts/src/ServerContext/streams';
import { useContext, useMemo } from 'react';
import { ServerContext } from '../ServerContext';
export const useStream = (
streamName: string,
export type ServerStreamFunction<StreamName extends ServerStreamerNames> = (
args: StreamerEvents[StreamName],
callback: (...args: any) => void,
) => () => void;
export function useStream<StreamName extends string>(
streamName: StreamName extends ServerStreamerNames ? never : StreamName,
options?: {
retransmit?: boolean;
retransmitToSelf?: boolean;
},
): (key: string, cb: (...args: unknown[]) => void) => () => void;
export function useStream<StreamName extends ServerStreamerNames>(
streamName: StreamName,
options?: {
retransmit?: boolean | undefined;
retransmitToSelf?: boolean | undefined;
},
): StreamerEvents[StreamName];
export function useStream<StreamName extends ServerStreamerNames>(
streamName: StreamName,
options?: {
retransmit?: boolean | undefined;
retransmitToSelf?: boolean | undefined;
},
): (<TEvent extends unknown[]>(eventName: string, callback: (...event: TEvent) => void) => () => void) => {
): StreamerEvents[StreamName] {
const { getStream } = useContext(ServerContext);
return useMemo(() => getStream(streamName, options), [getStream, streamName, options]);
};
}
/*
* @param streamName The name of the stream to subscribe to
* @returns A function that can be used to subscribe to the stream
* the main difference between this and useStream is that this function
* will only subscribe to the `stream + key` only once, but you can still add multiple callbacks
* to the same path
*/
export function useSingleStream<StreamName extends ServerStreamerNames>(
streamName: StreamName,
options?: {
retransmit?: boolean | undefined;
retransmitToSelf?: boolean | undefined;
},
): StreamerEvents[StreamName] {
const { getSingleStream } = useContext(ServerContext);
return useMemo(() => getSingleStream(streamName, options), [getSingleStream, streamName, options]);
}

@ -64,7 +64,7 @@ export { useSettings } from './hooks/useSettings';
export { useSettingsDispatch } from './hooks/useSettingsDispatch';
export { useSettingSetValue } from './hooks/useSettingSetValue';
export { useSettingStructure } from './hooks/useSettingStructure';
export { useStream } from './hooks/useStream';
export { useStream, useSingleStream } from './hooks/useStream';
export { useToastMessageDispatch } from './hooks/useToastMessageDispatch';
export { useTooltipClose } from './hooks/useTooltipClose';
export { useTooltipOpen } from './hooks/useTooltipOpen';
@ -86,6 +86,7 @@ export { useSetOutputMediaDevice } from './hooks/useSetOutputMediaDevice';
export { useSetInputMediaDevice } from './hooks/useSetInputMediaDevice';
export { ServerMethods, ServerMethodName, ServerMethodParameters, ServerMethodReturn, ServerMethodFunction } from './ServerContext/methods';
export { StreamerEvents } from './ServerContext/streams';
export { UploadResult } from './ServerContext';
export { TranslationKey, TranslationLanguage } from './TranslationContext';
export { Fields } from './UserContext';

Loading…
Cancel
Save