diff --git a/tdrive/frontend/src/app/components/connection-indicator/connection-indicator.tsx b/tdrive/frontend/src/app/components/connection-indicator/connection-indicator.tsx index 43dc4334..fe9161ef 100644 --- a/tdrive/frontend/src/app/components/connection-indicator/connection-indicator.tsx +++ b/tdrive/frontend/src/app/components/connection-indicator/connection-indicator.tsx @@ -6,20 +6,11 @@ import CheckCircleIcon from '@material-ui/icons/CheckCircle'; import Languages from '@features/global/services/languages-service'; import { ConnectedState } from '@features/users/state/atoms/connected'; import { useRecoilState } from 'recoil'; -import WebSocket, { WebsocketEvents } from '@features/global/types/websocket-types'; +// import WebSocket, { WebsocketEvents } from '@features/global/types/websocket-types'; export default () => { const [{ connected, reconnecting }, setState] = useRecoilState(ConnectedState); - useEffect(() => { - WebSocket.get().on(WebsocketEvents.Disconnected, () => { - setState({ connected: false, reconnecting: false }); - }); - WebSocket.get().on(WebsocketEvents.Connected, () => { - setState({ connected: true, reconnecting: false }); - }); - }, [setState]); - return (
{connected === false && reconnecting !== true && ( diff --git a/tdrive/frontend/src/app/features/applications/api/company-applications-api-client.ts b/tdrive/frontend/src/app/features/applications/api/company-applications-api-client.ts index aeaff57f..59bc9297 100644 --- a/tdrive/frontend/src/app/features/applications/api/company-applications-api-client.ts +++ b/tdrive/frontend/src/app/features/applications/api/company-applications-api-client.ts @@ -1,28 +1,20 @@ import Api from '../../global/framework/api-service'; import { TdriveService } from '../../global/framework/registry-decorator-service'; import { Application } from 'app/features/applications/types/application'; -import { WebsocketRoom } from '../../global/types/websocket-types'; const PREFIX = '/internal/services/applications/v1/companies'; @TdriveService('CompanyApplicationsAPIClientService') class CompanyApplicationsAPIClient { - private realtime: Map = new Map(); - - websockets(companyId: string): WebsocketRoom[] { - return this.realtime.get(companyId) || []; - } - /** * Get all applications for a company * * @param companyId */ async list(companyId: string): Promise { - return Api.get<{ resources: Application[]; websockets: WebsocketRoom[] }>( + return Api.get<{ resources: Application[] }>( `${PREFIX}/${companyId}/applications`, ).then(result => { - this.realtime.set(companyId, result.websockets); return result.resources && result.resources.length ? result.resources : []; }); } diff --git a/tdrive/frontend/src/app/features/applications/hooks/use-company-applications.ts b/tdrive/frontend/src/app/features/applications/hooks/use-company-applications.ts index 35fc810a..81e142e1 100644 --- a/tdrive/frontend/src/app/features/applications/hooks/use-company-applications.ts +++ b/tdrive/frontend/src/app/features/applications/hooks/use-company-applications.ts @@ -9,7 +9,6 @@ import { } from '../state/company-applications'; import CompanyApplicationsAPIClient from 'app/features/applications/api/company-applications-api-client'; import { useCurrentCompany } from '../../companies/hooks/use-companies'; -import { useRealtimeRoom } from 'app/features/global/hooks/use-realtime'; import { Application } from 'app/features/applications/types/application'; import { LoadingState } from 'app/features/global/state/atoms/Loading'; import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; @@ -102,18 +101,6 @@ export function useCompanyApplications(companyId = '') { }; } -export const useCompanyApplicationsRealtime = (companyId = '') => { - const { company } = useCurrentCompany(); - companyId = companyId || company?.id || ''; - - const { refresh } = useCompanyApplications(companyId); - - const room = CompanyApplicationsAPIClient.websockets(companyId || '')[0]; - useRealtimeRoom(room, 'useCompanyApplications', () => { - refresh(); - }); -}; - /** * Use a single application * @param applicationId diff --git a/tdrive/frontend/src/app/features/applications/services/application-service.ts b/tdrive/frontend/src/app/features/applications/services/application-service.ts index a144741d..e6d139ef 100644 --- a/tdrive/frontend/src/app/features/applications/services/application-service.ts +++ b/tdrive/frontend/src/app/features/applications/services/application-service.ts @@ -4,7 +4,6 @@ import AccessRightsService from 'app/features/workspace-members/services/workspa import CurrentUser from 'app/deprecated/user/CurrentUser'; import JWT from 'app/features/auth/jwt-storage-service'; import LocalStorage from '../../global/framework/local-storage-service'; -import WebSocket from '../../global/types/websocket-types'; class Application { private logger: Logger.Logger; @@ -25,9 +24,6 @@ class Application { this.started = true; this.logger.info('Starting application for user', user); - const ws = WebSocket.get(); - ws.connect(); - AccessRightsService.resetLevels(); CurrentUser.start(); } diff --git a/tdrive/frontend/src/app/features/companies/api/company-api-client.ts b/tdrive/frontend/src/app/features/companies/api/company-api-client.ts index b75f0891..6663b537 100644 --- a/tdrive/frontend/src/app/features/companies/api/company-api-client.ts +++ b/tdrive/frontend/src/app/features/companies/api/company-api-client.ts @@ -2,7 +2,6 @@ import { CompanyType } from '@features/companies/types/company'; import { WorkspaceType } from '@features/workspaces/types/workspace'; import Api from '../../global/framework/api-service'; import { TdriveService } from '../../global/framework/registry-decorator-service'; -import { WebsocketRoom } from '../../global/types/websocket-types'; const PREFIX = '/internal/services/users/v1'; @@ -14,12 +13,6 @@ export type UpdateWorkspaceBody = { @TdriveService('CompanyAPIClientService') class CompanyAPIClient { - private realtime: Map = new Map(); - - websocket(companyId: string): WebsocketRoom { - return this.realtime.get(companyId) || { room: '', token: '' }; - } - /** * Get a list of companies for a user, only common companies with current user are returned. @@ -37,13 +30,12 @@ class CompanyAPIClient { * @param companyId */ async get(companyId: string, disableJWTAuthentication = false): Promise { - return Api.get<{ resource: CompanyType; websocket: WebsocketRoom }>( + return Api.get<{ resource: CompanyType; }>( `${PREFIX}/companies/${companyId}`, undefined, false, { disableJWTAuthentication }, ).then(result => { - result.resource?.id && this.realtime.set(result.resource.id, result.websocket); return result.resource; }); } diff --git a/tdrive/frontend/src/app/features/companies/hooks/use-companies.ts b/tdrive/frontend/src/app/features/companies/hooks/use-companies.ts index 01406a40..92765f8f 100644 --- a/tdrive/frontend/src/app/features/companies/hooks/use-companies.ts +++ b/tdrive/frontend/src/app/features/companies/hooks/use-companies.ts @@ -12,7 +12,6 @@ import WorkspacesService from '@deprecated/workspaces/workspaces.jsx'; import AccessRightsService from '@features/workspace-members/services/workspace-members-access-rights-service'; import LoginService from '@features/auth/login-service'; import UserAPIClient from '@features/users/api/user-api-client'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; import CompanyAPIClient from '@features/companies/api/company-api-client'; /** @@ -86,16 +85,6 @@ export const useCurrentCompany = () => { return { company: company as CompanyType, refresh }; }; - -export const useCurrentCompanyRealtime = () => { - const { company, refresh } = useCurrentCompany(); - - const room = CompanyAPIClient.websocket(company?.id || ''); - useRealtimeRoom(room, 'useCurrentCompany', () => { - refresh(); - }); -}; - /** * Company priority: * 1. Router company id diff --git a/tdrive/frontend/src/app/features/drive/hooks/use-drive-realtime.tsx b/tdrive/frontend/src/app/features/drive/hooks/use-drive-realtime.tsx deleted file mode 100644 index c9698103..00000000 --- a/tdrive/frontend/src/app/features/drive/hooks/use-drive-realtime.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; -import { useDriveActions } from './use-drive-actions'; -import { useDriveItem } from './use-drive-item'; - -export const useDriveRealtime = (id: string) => { - const { refresh } = useDriveActions(); - const { websockets } = useDriveItem(id); - const room = websockets?.[0]; - useRealtimeRoom(room as { room: string; token: string }, 'useDriveRealtime-' + id, () => { - refresh(id); - }); -}; - -export const DriveRealtimeObject = ({ id }: { id: string }) => { - useDriveRealtime(id); - return <>; -}; diff --git a/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts b/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts deleted file mode 100644 index 6c12759a..00000000 --- a/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useCallback, useEffect, useRef, useState } from 'react'; -import useWebSocket from '@features/global/hooks/use-websocket'; -import Logger from '@features/global/framework/logger-service'; -import { - RealtimeBaseAction, - RealtimeBaseEvent, - RealtimeResourceEvent, -} from '../types/realtime-types'; -import { WebsocketRoom } from '@features/global/types/websocket-types'; - -const logger = Logger.getLogger('useRealtimeRoom'); - -export type RealtimeRoomService = { - lastEvent: T; - send: (data: T) => void; - emit: (event: string, data: T) => void; -}; - -/** - * Subscribe to a room using websocket channel. - * - * Note: It will subscribe only once, even if the component using it re renders. If you need to unsubscribe and subscribe again, call unsubscribe on the returned object. - * - * @param roomConf - * @param tagName - * @param onEvent - * @returns - */ -const useRealtimeRoom = ( - roomConf: WebsocketRoom, - tagName: string, - onEvent: (action: RealtimeBaseAction, event: T) => void, -) => { - const { websocket } = useWebSocket(); - const [lastEvent, setLastEvent] = useState<{ action: RealtimeBaseAction; payload: T }>(); - const [room, setRoom] = useState(roomConf); - const [tag] = useState(tagName); - // subscribe once - const subscribed = useRef(false); - - const newEvent = useCallback( - (event: { action: RealtimeBaseAction; payload: T }) => { - if (event) { - setLastEvent(event); - onEvent(event.action, event.payload); - } - }, - [onEvent], - ); - - useEffect(() => { - if (room !== roomConf) { - setRoom({ ...roomConf }); - if (room && subscribed.current && websocket) { - websocket.leave(room.room, tag); - subscribed.current = false; - } - } - }, [roomConf?.room, roomConf?.token, tagName]); - - useEffect(() => { - if (room && room.room && websocket && !subscribed.current) { - websocket.join(room.room, room.token, tag, (type: string, event: RealtimeBaseEvent) => { - logger.debug('Received WebSocket event', type, event); - if (type === 'realtime:resource') { - newEvent({ - action: (event as RealtimeResourceEvent).action, - payload: { - ...(event as RealtimeResourceEvent).resource, - _type: (event as RealtimeResourceEvent).type, - }, - }); - } else if (type === 'realtime:event') { - newEvent({ action: 'event', payload: event.data }); - } else if (type === 'realtime:join:success') { - logger.debug(`Room ${room} has been joined`); - } else { - logger.debug('Event type is not supported', type); - } - }); - subscribed.current = true; - } - }, [websocket, tag, room, onEvent]); - - return { - lastEvent, - send: (data: any) => websocket?.send(room.room, room.token, data), - unsubscribe: () => { - subscribed.current = false; - websocket?.leave(room.room, tagName); - }, - }; -}; - -export { useRealtimeRoom }; diff --git a/tdrive/frontend/src/app/features/global/hooks/use-websocket.ts b/tdrive/frontend/src/app/features/global/hooks/use-websocket.ts deleted file mode 100644 index 4eba4d17..00000000 --- a/tdrive/frontend/src/app/features/global/hooks/use-websocket.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import WebSocketFactory, { WebsocketEvents } from '@features/global/types/websocket-types'; -import WebSocketService from '@features/global/services/websocket-service'; - -const useWebSocket = () => { - const wsRef = useRef(); - // having this will allow consumers to be updated with the io instance once connected - const [connected, setConnected] = useState(false); - - useEffect(() => { - wsRef.current = WebSocketFactory.get(); - - if (wsRef.current.isConnected()) { - setConnected(() => true); - } - - wsRef.current.on(WebsocketEvents.Connected, () => { - setConnected(() => true); - }); - - wsRef.current.on(WebsocketEvents.Disconnected, () => { - setConnected(() => false); - }); - - return () => undefined; - }, []); - - return { - websocket: wsRef.current, - connected, - }; -}; - -export default useWebSocket; diff --git a/tdrive/frontend/src/app/features/global/services/websocket-factory-service.ts b/tdrive/frontend/src/app/features/global/services/websocket-factory-service.ts deleted file mode 100644 index d51872d5..00000000 --- a/tdrive/frontend/src/app/features/global/services/websocket-factory-service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Globals from './globals-tdrive-app-service'; -import Logger from '../framework/logger-service'; -import JWT from '../../auth/jwt-storage-service'; -import WebSocketService, { WebSocketOptions } from './websocket-service'; - -class WebSocketFactory { - private logger: Logger.Logger; - private instance!: WebSocketService; - - constructor() { - this.logger = Logger.getLogger('WebSocketFactory'); - } - - get(): WebSocketService { - if (!this.instance) { - this.instance = new WebSocketService(this.getOptions()); - } - - return this.instance; - } - - private getOptions(): WebSocketOptions { - return { - url: Globals.environment.websocket_url, - authenticateHandler: async () => { - let token = JWT.getJWT(); - - if (JWT.isAccessExpired()) { - try { - token = (await JWT.renew()).value; - } catch (err) { - this.logger.error('Can not get a new JWT token for WS collection', err); - } - } - - return { - token, - }; - }, - }; - } -} - -export default new WebSocketFactory(); diff --git a/tdrive/frontend/src/app/features/global/services/websocket-service.ts b/tdrive/frontend/src/app/features/global/services/websocket-service.ts deleted file mode 100644 index 331fbc7c..00000000 --- a/tdrive/frontend/src/app/features/global/services/websocket-service.ts +++ /dev/null @@ -1,261 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import io from 'socket.io-client'; -import { EventEmitter } from 'events'; -import Logger from '@features/global/framework/logger-service'; -import { WebsocketEvents, WebSocketListener, WebsocketRoomActions } from '../types/websocket-types'; -import { Maybe } from '@features/global/types/global-types'; - -export type WebSocketOptions = { - url: string; - authenticateHandler: () => Promise; -}; - -const CONNECT_TIMEOUT = 30000; - -class WebSocketService extends EventEmitter { - private logger: Logger.Logger; - private lastConnection = 0; - private wsListeners: { - [path: string]: { [tag: string]: WebSocketListener }; - } = {}; - private socket: SocketIOClient.Socket | null = null; - private connectTimeout?: ReturnType; - - constructor(private options: WebSocketOptions) { - super(); - this.logger = Logger.getLogger('WebsocketService'); - this.addEventListeners(); - } - - private addEventListeners(): void { - const reconnectWhenNeeded = () => { - if (!this.isConnected()) { - this.connect(); - } - }; - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - reconnectWhenNeeded(); - } - }); - - document.addEventListener('focus', () => { - reconnectWhenNeeded(); - }); - - setInterval(() => { - if (new Date().getTime() - this.lastConnection > CONNECT_TIMEOUT) { - this.lastConnection = new Date().getTime(); - reconnectWhenNeeded(); - } - }, CONNECT_TIMEOUT); - } - - isConnected(): boolean { - return this.socket?.connected || false; - } - - async connect(): Promise { - let connected: (value: boolean) => void; - const promise = new Promise(resolve => { - connected = resolve; - }); - - if (!this.options.url) { - this.logger.info('Skipping connect to empty URL'); - return false; - } - - this.logger.debug('Connecting to websocket', this.options.url); - - if (this.socket) { - if (this.socket.connected) { - this.logger.debug('Already connected to', this.options.url); - return false; - } else { - this.socket?.close(); - this.socket = null; - } - } - - if (!this.options.authenticateHandler) { - this.logger.error('Cannot connect without an authentication method'); - return false; - } - - this.socket = io.connect(this.options.url || '', { - path: '/socket', - reconnectionDelayMax: 10000, - reconnectionDelay: 2000, - }); - - this.socket.on('disconnect', () => { - this.logger.debug('Disconnected from websocket, socket.io will reconnect'); - }); - - this.socket.on('connect', async () => { - this.logger.debug('Connected to websocket, authenticating...'); - if (this.connectTimeout) { - clearTimeout(this.connectTimeout); - } - - this.socket - ?.emit('authenticate', (await this.options.authenticateHandler()) || {}) - .on('authenticated', () => { - this.logger.debug('Authenticated'); - this.rejoinAll(true); - connected(true); - this.emit(WebsocketEvents.Connected, { url: this.options.url }); - }) - .on('unauthorized', (err: any) => { - this.logger.warn('Websocket is not authorized', err); - this.socket?.close(); - this.socket = null; - //Retry and expect new jwt - this.connectTimeout = setTimeout(async () => { - const isConnected = await this.connect(); - if (isConnected) { - connected(true); - } - }, 1000); - }); - - this.socket?.on(WebsocketEvents.JoinSuccess, (event: any) => { - this.logger.debug('Websocket join success', event.name); - event.name && this.notify(event.name, WebsocketEvents.JoinSuccess, event); - }); - - this.socket?.on(WebsocketEvents.JoinError, (event: any) => { - this.logger.debug('Websocket join error', event.name); - event.name && this.notify(event.name, WebsocketEvents.JoinError, event); - }); - - this.socket?.on(WebsocketEvents.Resource, (event: any) => { - this.logger.debug('Received resource on room', event.room, event); - event.room && this.notify(event.room, WebsocketEvents.Resource, event); - }); - - this.socket?.on(WebsocketEvents.Event, (event: any) => { - this.logger.debug('New Websocket event', event.name); - event.name && this.notify(event.name, WebsocketEvents.Event, event); - }); - - this.socket?.on('disconnect', () => { - this.emit(WebsocketEvents.Disconnected, { url: this.options.url }); - }); - }); - - return promise; - } - - private rejoinAll(newlyConnected = false) { - Object.keys(this.wsListeners).forEach(key => { - Object.keys(this.wsListeners[key]).forEach(tag => { - if (this.wsListeners[key][tag]) { - newlyConnected && this.wsListeners[key][tag].callback(WebsocketEvents.Connected, {}); - this.join( - key, - this.wsListeners[key][tag].token, - tag, - this.wsListeners[key][tag].callback, - ); - } - }); - }); - } - - private notify(path: string, type: WebsocketEvents, event: any) { - if (this.wsListeners[path]) { - Object.values(this.wsListeners[path]).forEach(callback => callback.callback?.(type, event)); - } - } - - public getSocket(): SocketIOClient.Socket | null { - return this.socket; - } - - /** - * Join a room. callback will be called when a message is received on the given room - * - * @param path - * @param tag - * @param callback - */ - public join( - path: string, - token: string, - tag: string, - callback: (type: WebsocketEvents, event: any) => void, - ) { - const name = path.replace(/\/$/, ''); - - this.logger.debug(`Join room with name='${name}' and tag='${tag}'`); - - if (this.socket) { - this.socket.emit(WebsocketRoomActions.Join, { name, token }); - } - - this.wsListeners[name] = this.wsListeners[name] || {}; - this.wsListeners[name][tag] = { token, callback }; - } - - /** - * Leave a room - * - * @param path - * @param tag - */ - public leave(path: string, tag: string) { - const name = path.replace(/\/$/, ''); - - this.wsListeners[name] = this.wsListeners[name] || {}; - delete this.wsListeners[name][tag]; - - if (Object.keys(this.wsListeners[name]).length === 0) { - if (this.socket) { - this.logger.debug(`Leave room with name='${name}' and tag='${tag}'`); - this.socket.emit(WebsocketRoomActions.Leave, { name }); - } - delete this.wsListeners[name]; - } - } - - /** - * Send data as {name: path, data} in the realtime:event topic. - * - * @param path - * @param data - */ - public send(path: string, token: string, data: T): void { - const name = path.replace(/\/$/, ''); - this.logger.debug(`Send realtime:event with name='${name}'`); - - if (this.socket) { - this.socket.emit('realtime:event', { name, data, token }); - } - } - - public async get( - route: string, - request: Request, - callback?: (response: Response) => void, - ): Promise> { - this.logger.debug(`Get ${route}`); - - return new Promise>(resolve => { - if (this.socket) { - this.socket.emit(route, { data: request }, (response: { data: Response }) => { - this.logger.trace('Got a socket ack'); - const result = response.data; - callback && callback(result); - resolve(result); - }); - - return; - } - }); - } -} - -export default WebSocketService; diff --git a/tdrive/frontend/src/app/features/global/types/realtime-types.ts b/tdrive/frontend/src/app/features/global/types/realtime-types.ts deleted file mode 100644 index b4af1b70..00000000 --- a/tdrive/frontend/src/app/features/global/types/realtime-types.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export type WebSocketResource = { - room: string; - name?: string; -}; - -export type RealtimeResources = { - resources: T[]; - websockets: WebSocketResource[]; -}; - -export type RealtimeApplicationEventAction = 'configure' | 'close_configure'; -export type RealtimeEventAction = 'saved' | 'updated' | 'deleted' | 'event'; - -export type RealtimeBaseAction = RealtimeEventAction | RealtimeApplicationEventAction; - -export interface RealtimeBaseEvent { - action: RealtimeEventAction; - data?: any; -} - -export interface RealtimeResourceEvent extends RealtimeBaseEvent { - resource: T; - type?: string; -} - -export interface RealtimeEvent extends RealtimeBaseEvent { - room: string; - path: string; - type: U; - resource: T; -} - -export interface RealtimeApplicationEvent { - action: 'configure' | 'close_configure'; - connection_id: string; - application: unknown; - form: unknown; - hidden_data: unknown; - configurator_id: string; -} diff --git a/tdrive/frontend/src/app/features/global/types/websocket-types.ts b/tdrive/frontend/src/app/features/global/types/websocket-types.ts deleted file mode 100644 index 59e8873e..00000000 --- a/tdrive/frontend/src/app/features/global/types/websocket-types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import WebSocketFactory from '../services/websocket-factory-service'; - -export type WebsocketRoom = { - room: string; - token: string; -}; - -export enum WebsocketRoomActions { - Join = 'realtime:join', - Leave = 'realtime:leave', -} - -export enum WebsocketEvents { - Connected = 'connected', - Connecting = 'connecting', - Disconnected = 'disconnected', - JoinSuccess = 'realtime:join:success', - JoinError = 'realtime:join:error', - Resource = 'realtime:resource', - Event = 'realtime:event', -} - -export type WebSocketListener = { - token: string; - callback: (type: WebsocketEvents, event: T) => void; -}; - -export default WebSocketFactory; diff --git a/tdrive/frontend/src/app/features/users/api/online-user-realtime-api-client.ts b/tdrive/frontend/src/app/features/users/api/online-user-realtime-api-client.ts deleted file mode 100644 index f0dde082..00000000 --- a/tdrive/frontend/src/app/features/users/api/online-user-realtime-api-client.ts +++ /dev/null @@ -1,47 +0,0 @@ -import WebSocketService from '../../global/services/websocket-service'; -import Logger from '../../global/framework/logger-service'; -import UsersService from '@features/users/services/current-user-service'; -const logger = Logger.getLogger('OnlineUsersRealtimeAPI'); - -export type GetUserRequestType = string; -export type GetUsersRequestType = Array; -export type GetUserResponseType = [string, boolean]; -export type GetUsersResponseType = Array; -export type RealtimeUpdateMessageType = GetUsersResponseType; - -export type OnlineUserRealtimeAPIType = { - getUserStatus: (id: GetUserRequestType) => Promise; - getUsersStatus: (ids: GetUsersRequestType) => Promise; - setMyStatus: () => void; -}; - -export const ONLINE_ROOM = (companyId: string) => `/users/online/${companyId}`; - -export const OnlineUserRealtimeAPI = (websocket: WebSocketService): OnlineUserRealtimeAPIType => { - const getUserStatus = (id: GetUserRequestType): Promise => { - logger.debug(`Get user online status ${id}`); - return getUsersStatus([id]).then(response => response?.[0] || [id, false]); - }; - - const getUsersStatus = (ids: GetUsersRequestType = []): Promise => { - logger.debug(`Get users statuses ${ids.join(',')}`); - return websocket - .get('online:get', ids) - .then(result => result || []); - }; - - const setMyStatus = (): void => { - const id = UsersService.getCurrentUserId(); - logger.debug(`Set user online status ${id}`); - - websocket - .get('online:set', [id]) - .then(result => result || []); - }; - - return { - getUserStatus, - getUsersStatus, - setMyStatus, - }; -}; diff --git a/tdrive/frontend/src/app/features/users/api/user-api-client.ts b/tdrive/frontend/src/app/features/users/api/user-api-client.ts index 40235a70..43da3018 100644 --- a/tdrive/frontend/src/app/features/users/api/user-api-client.ts +++ b/tdrive/frontend/src/app/features/users/api/user-api-client.ts @@ -3,7 +3,6 @@ import { CompanyType } from '@features/companies/types/company'; import { UserPreferencesType, UserType } from '@features/users/types/user'; import Api from '../../global/framework/api-service'; import { TdriveService } from '../../global/framework/registry-decorator-service'; -import { WebsocketRoom } from '../../global/types/websocket-types'; import WorkspaceAPIClient from '../../workspaces/api/workspace-api-client'; import CurrentUser from '../../../deprecated/user/CurrentUser'; import { setUserList } from '../hooks/use-user-list'; @@ -23,13 +22,8 @@ type SearchUserApiResponse = { @TdriveService('UserAPIClientService') class UserAPIClientService { private readonly prefixUrl: string = '/internal/services/users/v1'; - private realtime: Map = new Map(); private logger = Logger.getLogger('UserAPIClientService'); - websocket(userId: string): WebsocketRoom { - return this.realtime.get(userId) || { room: '', token: '' }; - } - /** * Get users from their ID * @@ -101,7 +95,6 @@ class UserAPIClientService { companyId: string, options?: { bufferize?: boolean; callback?: (res: UserType[]) => void }, ): Promise { - const res = await new Promise(resolve => Api.post( `/internal/services/users/v1/users/${companyId}/all`, @@ -135,7 +128,7 @@ class UserAPIClientService { } async getCurrent(disableJWTAuthentication = false): Promise { - return Api.get<{ resource: UserType; websocket: WebsocketRoom }>( + return Api.get<{ resource: UserType }>( '/internal/services/users/v1/users/me', undefined, false, @@ -144,7 +137,6 @@ class UserAPIClientService { if (!result?.resource?.id) { throw new Error('User not found'); } - result.resource.id && this.realtime.set(result.resource.id, result.websocket); return result.resource; }); } diff --git a/tdrive/frontend/src/app/features/users/api/user-notification-api-client.ts b/tdrive/frontend/src/app/features/users/api/user-notification-api-client.ts index 6f15bbbf..25ad3d3b 100644 --- a/tdrive/frontend/src/app/features/users/api/user-notification-api-client.ts +++ b/tdrive/frontend/src/app/features/users/api/user-notification-api-client.ts @@ -1,29 +1,20 @@ import Api from '../../global/framework/api-service'; import { NotificationType } from '@features/users/types/notification-types'; import { TdriveService } from '../../global/framework/registry-decorator-service'; -import { WebsocketRoom } from '@features/global/types/websocket-types'; @TdriveService('UserNotificationAPIClientService') class UserNotificationAPIClient { - private realtime: WebsocketRoom = { room: '', token: '' }; - - websocket(): WebsocketRoom { - return this.realtime; - } - async getAllCompaniesBadges(): Promise { - const response = await Api.get<{ resources: NotificationType[]; websockets: WebsocketRoom[] }>( + const response = await Api.get<{ resources: NotificationType[] }>( '/internal/services/notifications/v1/badges?limit=1000&websockets=1&all_companies=true', ); - if (response.websockets) this.realtime = response.websockets[0]; return response.resources ? response.resources : []; } async getCompanyBadges(companyId: string): Promise { - const response = await Api.get<{ resources: NotificationType[]; websockets: WebsocketRoom[] }>( + const response = await Api.get<{ resources: NotificationType[] }>( '/internal/services/notifications/v1/badges?limit=1000&websockets=1&company_id=' + companyId, ); - if (response.websockets) this.realtime = response.websockets[0]; return response.resources ? response.resources : []; } diff --git a/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts b/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts index 16a9af38..e659ef7f 100644 --- a/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts +++ b/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts @@ -4,7 +4,6 @@ import LoginService from '@features/auth/login-service'; import UserAPIClient from '@features/users/api/user-api-client'; import { useRecoilState } from 'recoil'; import { CurrentUserState } from '../state/atoms/current-user'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; import Languages from '@features/global/services/languages-service'; import { useSetUserList } from './use-user-list'; import { getPublicLinkToken } from 'app/features/drive/api-client/api-client'; @@ -46,23 +45,3 @@ export const useCurrentUser = () => { return { user, refresh }; }; - -export const useCurrentUserRealtime = () => { - const { user, refresh } = useCurrentUser(); - const room = UserAPIClient.websocket(user?.id || ''); - - const timeout = useRef(0); - - useRealtimeRoom(room, 'useCurrentUser', async (action, resource) => { - switch (resource._type) { - case 'user': - clearTimeout(timeout.current); // - timeout.current = setTimeout(() => { - refresh(); - }, 1000) as any; - break; - default: - console.error('Unknown resource type'); - } - }); -}; diff --git a/tdrive/frontend/src/app/features/users/hooks/use-notifications.ts b/tdrive/frontend/src/app/features/users/hooks/use-notifications.ts deleted file mode 100644 index 7f95b701..00000000 --- a/tdrive/frontend/src/app/features/users/hooks/use-notifications.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useGlobalEffect } from '@features/global/hooks/use-global-effect'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; -import useRouterCompany from '@features/router/hooks/use-router-company'; -import _ from 'lodash'; -import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; -import userNotificationApiClient from '../api/user-notification-api-client'; -import { - NotificationsBadgesState, - NotificationsChannelBadgesSelector, - NotificationsCompanyBadgesSelector, - NotificationsOtherCompanyBadgesSelector, - NotificationsWorkspaceBadgesSelector, -} from '../state/atoms/notifications'; -import { NotificationType } from '../types/notification-types'; -import ElectronService from '@features/global/framework/electron-service'; -import windowState from '@features/global/utils/window'; -import RouterService, { ClientStateType } from '../../router/services/router-service'; -import { pushDesktopNotification } from '../services/push-desktop-notification'; -import { RouterState } from '@features/router/state/atoms/router'; -import { useCurrentUser } from '@features/users/hooks/use-current-user'; - -export let removeBadgesNow: (type: 'company' | 'workspace' | 'channel', id: string) => void = () => - undefined; - -const executeRequest = () => { - if ('Notification' in window && window.Notification.requestPermission) { - const request = window.Notification.requestPermission(); - if (request && request.then) { - request - .then(() => undefined) - .finally(() => { - window.removeEventListener('click', executeRequest); - }); - } - } -}; -window.addEventListener('click', executeRequest); -executeRequest(); - -export const useNotifications = () => { - const [badges, setBadges] = useRecoilState(NotificationsBadgesState); - const companyId = useRouterCompany(); - - const addBadges = useRecoilCallback( - ({ snapshot }) => - (newBadges: NotificationType[]) => { - const badges = snapshot.getLoadable(NotificationsBadgesState).getValue(); - const list = _.uniqBy([...badges, ...newBadges], 'id'); - setBadges(list); - }, - [setBadges, badges], - ); - - const removeBadges = useRecoilCallback( - ({ snapshot }) => - (removedBadges: NotificationType[]) => { - const badges = snapshot.getLoadable(NotificationsBadgesState).getValue(); - const list = _.differenceBy(badges, removedBadges, 'id'); - setBadges(list); - }, - [setBadges, badges], - ); - - const removeObjectBadges = useRecoilCallback( - ({ snapshot }) => - (type: 'channel' | 'workspace' | 'company', id: string) => { - const badges = snapshot.getLoadable(NotificationsBadgesState).getValue(); - const list = badges.filter( - b => - (type === 'channel' && b.channel_id !== id) || - (type === 'workspace' && b.workspace_id !== id) || - (type === 'company' && b.company_id !== id), - ); - setBadges(list); - }, - [setBadges, badges], - ); - removeBadgesNow = removeObjectBadges; - - const setCompanyBadges = useRecoilCallback( - ({ snapshot }) => - (newBadges: NotificationType[], companyId: string) => { - const badges = snapshot.getLoadable(NotificationsBadgesState).getValue(); - const list = _.uniqBy( - [...badges.filter(b => b.company_id !== companyId), ...newBadges], - 'id', - ); - setBadges(list); - }, - [setBadges, badges], - ); - - useGlobalEffect( - 'useCompanyNotifications', - async () => { - if (companyId) { - const updatedBadges = await userNotificationApiClient.getCompanyBadges(companyId); - const updatedOtherCompaniesBadges = await userNotificationApiClient.getAllCompaniesBadges(); - setCompanyBadges([...updatedOtherCompaniesBadges, ...updatedBadges], companyId); - } - }, - [companyId], - ); - - useGlobalEffect( - 'useNotificationsSetWindowBadges', - () => { - let notifications = 0; - const state = RouterService.getStateFromRoute(); - const ignore: any = []; - for (const notification of badges) { - if ( - ignore.indexOf(notification.company_id) >= 0 || - ignore.indexOf(notification.workspace_id) >= 0 - ) - return; - - if ( - notification.company_id !== state.companyId || - (notification.workspace_id !== state.workspaceId && - notification.workspace_id !== 'direct') - ) { - notifications++; - ignore.push(notification.company_id); - ignore.push(notification.workspace_id); - } else { - notifications++; - } - } - - windowState.setPrefix(notifications); - if (notifications > 0) { - ElectronService.setBadge('' + notifications); - } else { - ElectronService.setBadge(''); - } - }, - [badges], - ); - - const { user } = useCurrentUser(); - const soundType = - user?.preference?.notifications?.[0]?.preferences?.notification_sound || 'default'; - const realtimeEvent = useRecoilCallback( - ({ snapshot }) => - async (action: string, resource: any) => { - const routerState = snapshot.getLoadable(RouterState).valueMaybe() as ClientStateType; - - if (action === 'event' && resource._type === 'notification:desktop') { - pushDesktopNotification({ ...resource, routerState }, soundType); - userNotificationApiClient.acknowledge(resource); - } - if (action === 'saved') addBadges([resource]); - if (action === 'deleted') removeBadges([resource]); - }, - [addBadges, removeBadges], - ); - const room = userNotificationApiClient.websocket(); - useRealtimeRoom(room, 'useNotifications', realtimeEvent); - - return { - badges, - }; -}; - -export const useCompanyNotifications = (id: string) => { - const badges = useRecoilValue(NotificationsCompanyBadgesSelector(id)); - return { - badges, - }; -}; - -export const useOtherCompanyNotifications = (id: string) => { - const badges = useRecoilValue(NotificationsOtherCompanyBadgesSelector(id)); - return { - badges, - }; -}; - -export const useWorkspaceNotifications = (id: string) => { - const badges = useRecoilValue(NotificationsWorkspaceBadgesSelector(id)); - return { - badges, - }; -}; - -export const useChannelNotifications = (id: string) => { - const badges = useRecoilValue(NotificationsChannelBadgesSelector(id)); - return { - badges, - }; -}; diff --git a/tdrive/frontend/src/app/features/users/hooks/use-online-users.ts b/tdrive/frontend/src/app/features/users/hooks/use-online-users.ts deleted file mode 100644 index b051f8d0..00000000 --- a/tdrive/frontend/src/app/features/users/hooks/use-online-users.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { useCallback, useEffect } from 'react'; -import { useRecoilState } from 'recoil'; - -import Logger from '@features/global/framework/logger-service'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; -import { OnlineUsersState, OnlineUserType } from '../state/atoms/online-users'; -import useWebSocket from '../../global/hooks/use-websocket'; -import { - OnlineUserRealtimeAPI, - ONLINE_ROOM, - RealtimeUpdateMessageType, -} from '../../../features/users/api/online-user-realtime-api-client'; -import JWTStorage from '../../auth/jwt-storage-service'; -import useRouterCompany from '@features/router/hooks/use-router-company'; - -const logger = Logger.getLogger('useOnlineUsers'); - -const GET_INTERVAL = 30000; //30 seconds -const SET_INTERVAL = 600000; //10 minutes -let getIntervalId = setTimeout(() => {}, 0); -let setIntervalId = setTimeout(() => {}, 0); - -export const useOnlineUsers = (): void => { - logger.trace('Running online hook'); - const { websocket } = useWebSocket(); - const companyId = useRouterCompany(); - const [onlineUsers, setOnlineUsersState] = useRecoilState(OnlineUsersState); - - const updateOnline = useCallback((statuses: Array<[string, boolean]> = []): void => { - logger.debug(`Update online status for ${statuses.length} users`, statuses); - const lastSeen = Date.now(); - setOnlineUsersState(users => { - if (!statuses.length) { - return users; - } - - const previousStateMap = new Map(users.map(u => [u.id!, u])); - - for (const statusTuple of statuses) { - previousStateMap.set(statusTuple[0], { - id: statusTuple[0], - connected: statusTuple[1], - lastSeen: statusTuple[1] ? lastSeen : previousStateMap.get(statusTuple[0])?.lastSeen || 0, - initialized: true, - }); - } - - return [...previousStateMap.entries()].map(entry => ({ - id: entry[0], - connected: entry[1].connected, - lastSeen: entry[1].lastSeen, - initialized: true, - })); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (websocket) { - // got some local users, ask if there are still online - const api = OnlineUserRealtimeAPI(websocket); - clearInterval(getIntervalId); - getIntervalId = setInterval(async () => { - const users = onlineUsers.map(u => u.id).filter((n?: T): n is T => Boolean(n)); - const status = await api.getUsersStatus(users); - updateOnline(status); - }, GET_INTERVAL); - - clearInterval(setIntervalId); - setIntervalId = setInterval(async () => await api.setMyStatus(), SET_INTERVAL); - } - - return () => { - getIntervalId && clearInterval(getIntervalId); - setIntervalId && clearInterval(setIntervalId); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [websocket, onlineUsers]); - - // listen to room events in which online events are pushed - useRealtimeRoom( - { room: ONLINE_ROOM(companyId), token: JWTStorage.getJWT() }, - 'useOnlineUsers' + companyId, - (action, resource: any) => { - delete resource['_type']; - resource = Object.values(resource?.online || resource); - if (action === 'event' && resource?.length) { - updateOnline(resource.map((u: any) => [u.user_id, u.is_online])); - } - }, - ); -}; diff --git a/tdrive/frontend/src/app/features/users/hooks/use-user.ts b/tdrive/frontend/src/app/features/users/hooks/use-user.ts index e566c476..0301ebc1 100644 --- a/tdrive/frontend/src/app/features/users/hooks/use-user.ts +++ b/tdrive/frontend/src/app/features/users/hooks/use-user.ts @@ -1,7 +1,6 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { useGlobalEffect } from '@features/global/hooks/use-global-effect'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; import { LoadingState } from '@features/global/state/atoms/Loading'; import { UserType } from '@features/users/types/user'; import UserAPIClient from '../api/user-api-client'; @@ -34,10 +33,5 @@ export const useUser = (userId: string): UserType | undefined => { [], ); - const room = UserAPIClient.websocket(userId); - useRealtimeRoom(room, 'useUser', () => { - refresh(); - }); - return user; }; diff --git a/tdrive/frontend/src/app/features/users/state/integration/user-context.tsx b/tdrive/frontend/src/app/features/users/state/integration/user-context.tsx index dc52adb1..13117ab5 100644 --- a/tdrive/frontend/src/app/features/users/state/integration/user-context.tsx +++ b/tdrive/frontend/src/app/features/users/state/integration/user-context.tsx @@ -1,11 +1,9 @@ // eslint-disable-next-line @typescript-eslint/no-use-before-define import React from 'react'; -import { useOnlineUsers } from '@features/users/hooks/use-online-users'; /** * This hook will be global to application */ export default (): JSX.Element => { - useOnlineUsers(); return <>; }; diff --git a/tdrive/frontend/src/app/features/workspaces/api/workspace-api-client.ts b/tdrive/frontend/src/app/features/workspaces/api/workspace-api-client.ts index df92420c..78dfd839 100644 --- a/tdrive/frontend/src/app/features/workspaces/api/workspace-api-client.ts +++ b/tdrive/frontend/src/app/features/workspaces/api/workspace-api-client.ts @@ -2,7 +2,6 @@ import Api from '../../global/framework/api-service'; import { CompanyType } from '@features/companies/types/company'; import { WorkspaceType } from '@features/workspaces/types/workspace'; import { TdriveService } from '../../global/framework/registry-decorator-service'; -import { WebsocketRoom } from '../../global/types/websocket-types'; import _ from 'lodash'; const PREFIX = '/internal/services/workspaces/v1/companies'; @@ -23,22 +22,15 @@ type UpdateWorkspaceInviteDomainResponse = { @TdriveService('WorkspaceAPIClientService') class WorkspaceAPIClient { - private realtime: Map = new Map(); - - websockets(companyId: string): WebsocketRoom[] { - return this.realtime.get(companyId) || []; - } - /** * Get all workspaces for a company * * @param companyId */ async list(companyId: string): Promise { - return Api.get<{ resources: WorkspaceType[]; websockets: WebsocketRoom[] }>( + return Api.get<{ resources: WorkspaceType[] }>( `${PREFIX}/${companyId}/workspaces?websockets=1`, ).then(result => { - this.realtime.set(companyId, result.websockets); return result.resources && result.resources.length ? result.resources : []; }); } diff --git a/tdrive/frontend/src/app/features/workspaces/hooks/use-workspaces.ts b/tdrive/frontend/src/app/features/workspaces/hooks/use-workspaces.ts index bfadf90d..a3390c12 100644 --- a/tdrive/frontend/src/app/features/workspaces/hooks/use-workspaces.ts +++ b/tdrive/frontend/src/app/features/workspaces/hooks/use-workspaces.ts @@ -2,15 +2,10 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { WorkspaceType } from '@features/workspaces/types/workspace'; import { WorkspaceListStateFamily } from '../state/workspace-list'; -import Collections from '@deprecated/CollectionsV1/Collections/Collections'; -import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; import useRouterWorkspace from '@features/router/hooks/use-router-workspace'; import RouterService from '@features/router/services/router-service'; import _ from 'lodash'; import WorkspacesService from '@deprecated/workspaces/workspaces.jsx'; -import AccessRightsService, { - RightsOrNone, -} from '@features/workspace-members/services/workspace-members-access-rights-service'; import LocalStorage from '@features/global/framework/local-storage-service'; import useRouterCompany from '@features/router/hooks/use-router-company'; import WorkspaceAPIClient from '@features/workspaces/api/workspace-api-client'; @@ -47,22 +42,6 @@ export const useWorkspacesCommons = (companyId = '') => { return { workspaces, loading, refresh }; }; -export function useWorkspaces(companyId = '') { - const { workspaces, loading, refresh } = useWorkspacesCommons(companyId); - - useRealtimeRoom( - WorkspaceAPIClient.websockets(companyId)[0], - 'useWorkspaces', - action => { - if (action === 'saved') { - refresh(); - } - }, - ); - - return { workspaces, loading, refresh }; -} - export function useWorkspaceLoader(companyId: string) { const loading = useRecoilValue(LoadingState(`workspaces-${companyId}`)); return { loading }; diff --git a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx index f8ae0e31..70111f99 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx @@ -6,7 +6,6 @@ import { getFilesTree } from '@components/uploads/file-tree-utils'; import UploadZone from '@components/uploads/upload-zone'; import { setTdriveTabToken } from '@features/drive/api-client/api-client'; import { useDriveItem } from '@features/drive/hooks/use-drive-item'; -import { DriveRealtimeObject } from '@features/drive/hooks/use-drive-realtime'; import { useDriveUpload } from '@features/drive/hooks/use-drive-upload'; import { DriveItemSelectedList } from '@features/drive/state/store'; import { formatBytes } from '@features/drive/utils'; @@ -257,7 +256,6 @@ export default memo( onDrop={handleDrop} > - {role == "admin" && } diff --git a/tdrive/frontend/src/app/views/client/index.tsx b/tdrive/frontend/src/app/views/client/index.tsx index 8f3b112c..ef2b3a15 100755 --- a/tdrive/frontend/src/app/views/client/index.tsx +++ b/tdrive/frontend/src/app/views/client/index.tsx @@ -9,7 +9,7 @@ import ModalComponent from '@components/modal/modal-component'; import PopupService from '@deprecated/popupManager/popupManager.js'; import useUsetiful from '@features/global/hooks/use-usetiful'; import Languages from '@features/global/services/languages-service'; -import { useCurrentUser, useCurrentUserRealtime } from '@features/users/hooks/use-current-user'; +import { useCurrentUser } from '@features/users/hooks/use-current-user'; import UserContext from '@features/users/state/integration/user-context'; import Viewer from '@views/client/viewer/viewer'; import ConnectionIndicator from 'components/connection-indicator/connection-indicator'; @@ -26,7 +26,6 @@ import SideBar from './side-bar'; export default React.memo((): JSX.Element => { const [menuIsOpen, setMenuIsOpen] = useState(false); const { user } = useCurrentUser(); - useCurrentUserRealtime(); const { FeatureToggles, activeFeatureNames } = useFeatureToggles(); useUsetiful();