diff --git a/package-lock.json b/package-lock.json index 60d8de28c8..7fcdb2d627 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39473,12 +39473,6 @@ "queue-tick": "^1.0.1" } }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true - }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -46658,7 +46652,6 @@ "sinon-mongoose": "^2.3.0", "socket.io-mock": "^1.3.1", "storybook": "^7.4.0", - "strict-event-emitter": "^0.5.1", "terser-webpack-plugin": "^5.3.9", "thread-loader": "^4.0.2", "timekeeper": "^2.2.0", @@ -55432,7 +55425,6 @@ "sinon-mongoose": "^2.3.0", "socket.io-mock": "^1.3.1", "storybook": "^7.4.0", - "strict-event-emitter": "^0.5.1", "terser-webpack-plugin": "^5.3.9", "thread-loader": "^4.0.2", "timekeeper": "^2.2.0", @@ -80671,12 +80663,6 @@ "queue-tick": "^1.0.1" } }, - "strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true - }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", diff --git a/services/web/frontend/js/features/ide-react/connection/connection-manager.ts b/services/web/frontend/js/features/ide-react/connection/connection-manager.ts index c3a6cb1730..98a474f350 100644 --- a/services/web/frontend/js/features/ide-react/connection/connection-manager.ts +++ b/services/web/frontend/js/features/ide-react/connection/connection-manager.ts @@ -1,7 +1,6 @@ import { ConnectionError, ConnectionState } from './types/connection-state' import SocketIoShim from '../../../ide/connection/SocketIoShim' import getMeta from '../../../utils/meta' -import { Emitter } from 'strict-event-emitter' import { Socket } from '@/features/ide-react/connection/types/socket' import { debugConsole } from '@/utils/debugging' @@ -29,11 +28,12 @@ const initialState: ConnectionState = { error: '', } -type Events = { - statechange: [{ state: ConnectionState; previousState: ConnectionState }] -} +export class StateChangeEvent extends CustomEvent<{ + state: ConnectionState + previousState: ConnectionState +}> {} -export class ConnectionManager extends Emitter { +export class ConnectionManager extends EventTarget { state: ConnectionState = initialState private connectionAttempt: number | null = null private gracefullyReconnectUntil = 0 @@ -113,7 +113,9 @@ export class ConnectionManager extends Emitter { previousState, state, }) - this.emit('statechange', { state, previousState }) + this.dispatchEvent( + new StateChangeEvent('statechange', { detail: { state, previousState } }) + ) } private onOnline() { diff --git a/services/web/frontend/js/features/ide-react/context/connection-context.tsx b/services/web/frontend/js/features/ide-react/context/connection-context.tsx index 7326e97c79..7ea8ff4d99 100644 --- a/services/web/frontend/js/features/ide-react/context/connection-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/connection-context.tsx @@ -8,7 +8,10 @@ import { useMemo, } from 'react' import { ConnectionState } from '../connection/types/connection-state' -import { ConnectionManager } from '@/features/ide-react/connection/connection-manager' +import { + ConnectionManager, + StateChangeEvent, +} from '@/features/ide-react/connection/connection-manager' import { Socket } from '@/features/ide-react/connection/types/socket' import { secondsUntil } from '@/features/ide-react/connection/utils' import { useLocation } from '@/shared/hooks/use-location' @@ -37,13 +40,13 @@ export const ConnectionProvider: FC = ({ children }) => { ) useEffect(() => { - function handleStateChange(event: { state: ConnectionState }) { - setConnectionState(event.state) - } - connectionManager.on('statechange', handleStateChange) + const handleStateChange = ((event: StateChangeEvent) => { + setConnectionState(event.detail.state) + }) as EventListener + connectionManager.addEventListener('statechange', handleStateChange) return () => { - connectionManager.off('statechange', handleStateChange) + connectionManager.removeEventListener('statechange', handleStateChange) } }, [connectionManager]) diff --git a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx index 3ee21ffe59..2e7f666a42 100644 --- a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx @@ -18,11 +18,10 @@ import { useEditorContext } from '@/shared/context/editor-context' import { useIdeContext } from '@/shared/context/ide-context' import useSocketListener from '@/features/ide-react/hooks/use-socket-listener' import useEventListener from '@/shared/hooks/use-event-listener' -import { FileTreeFindResult } from '@/features/ide-react/types/file-tree' -import { Project } from '../../../../../types/project' import { useModalsContext } from '@/features/ide-react/context/modals-context' import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' import { useTranslation } from 'react-i18next' +import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' type DocumentMetadata = { labels: string[] @@ -63,7 +62,9 @@ export const MetadataProvider: FC = ({ children }) => { const debouncerRef = useRef>(new Map()) // DocId => Timeout useEffect(() => { - const handleEntityDeleted = (entity: FileTreeFindResult) => { + const handleEntityDeleted = ({ + detail: [entity], + }: CustomEvent) => { if (entity.type === 'doc') { setDocuments(documents => { return _.omit(documents, entity.entity._id) @@ -184,7 +185,9 @@ export const MetadataProvider: FC = ({ children }) => { useEventListener('editor:metadata-outdated', handleMetadataOutdated) useEffect(() => { - const handleProjectJoined = ({ project }: { project: Project }) => { + const handleProjectJoined = ({ + detail: [{ project }], + }: CustomEvent) => { if (project.deletedByExternalDataSource) { showGenericMessageModal( t('project_renamed_or_deleted'), diff --git a/services/web/frontend/js/features/ide-react/context/online-users-context.tsx b/services/web/frontend/js/features/ide-react/context/online-users-context.tsx index c3ff903691..2da9df438f 100644 --- a/services/web/frontend/js/features/ide-react/context/online-users-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/online-users-context.tsx @@ -19,6 +19,7 @@ import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { findDocEntityById } from '@/features/ide-react/util/find-doc-entity-by-id' import useSocketListener from '@/features/ide-react/hooks/use-socket-listener' import { debugConsole } from '@/utils/debugging' +import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' type OnlineUser = { id: string @@ -213,7 +214,9 @@ export const OnlineUsersProvider: FC = ({ children }) => { // Track the position of the main cursor useEffect(() => { - const handleCursorUpdate = (position: CursorPosition | null) => { + const handleCursorUpdate = ({ + detail: [position], + }: CustomEvent) => { if (position) { setCurrentPosition(position) } diff --git a/services/web/frontend/js/features/ide-react/context/references-context.tsx b/services/web/frontend/js/features/ide-react/context/references-context.tsx index ea0f652c05..90463acde0 100644 --- a/services/web/frontend/js/features/ide-react/context/references-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/references-context.tsx @@ -18,6 +18,7 @@ import useScopeValue from '@/shared/hooks/use-scope-value' import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store' import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { findDocEntityById } from '@/features/ide-react/util/find-doc-entity-by-id' +import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' type References = { keys: string[] @@ -108,7 +109,9 @@ export const ReferencesProvider: FC = ({ children }) => { ) useEffect(() => { - const handleDocClosed = (doc: ShareJsDoc) => { + const handleDocClosed = ({ + detail: [doc], + }: CustomEvent) => { if ( doc.doc_id && findDocEntityById(fileTreeData, doc.doc_id)?.name?.endsWith('.bib') diff --git a/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts b/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts index 94127b238e..b130090afd 100644 --- a/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts +++ b/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts @@ -1,4 +1,3 @@ -import { Emitter } from 'strict-event-emitter' import { Project } from '../../../../types/project' import { PermissionsLevel } from '@/features/ide-react/types/permissions' import { ShareJsDoc } from '@/features/ide-react/editor/share-js-doc' @@ -26,8 +25,33 @@ export type IdeEvents = { 'entity:deleted': [entity: FileTreeFindResult] } -export type IdeEventEmitter = Emitter +export class IdeEventEmitter extends EventTarget { + emit(eventName: T, ...detail: IdeEvents[T]) { + this.dispatchEvent(new CustomEvent(eventName, { detail })) + } -export function createIdeEventEmitter(): IdeEventEmitter { - return new Emitter() + on( + eventName: T, + listener: (event: CustomEvent) => void + ) { + this.addEventListener(eventName, listener as EventListener) + } + + once( + eventName: T, + listener: (event: CustomEvent) => void + ) { + this.addEventListener(eventName, listener as EventListener, { once: true }) + } + + off( + eventName: T, + listener: (event: CustomEvent) => void + ) { + this.removeEventListener(eventName, listener as EventListener) + } +} + +export function createIdeEventEmitter() { + return new IdeEventEmitter() } diff --git a/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts b/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts index 96cdb2987c..5602dc2103 100644 --- a/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts +++ b/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts @@ -2,14 +2,13 @@ import { ScopeEventEmitter, ScopeEventName, } from '../../../../../types/ide/scope-event-emitter' -import EventEmitter from 'events' export class ReactScopeEventEmitter implements ScopeEventEmitter { // eslint-disable-next-line no-useless-constructor - constructor(private readonly eventEmitter: EventEmitter) {} + constructor(private readonly eventEmitter: EventTarget) {} emit(eventName: ScopeEventName, broadcast: boolean, ...detail: unknown[]) { - this.eventEmitter.emit(eventName, ...detail) + this.eventEmitter.dispatchEvent(new CustomEvent(eventName, { detail })) } on(eventName: ScopeEventName, listener: (...args: unknown[]) => void) { @@ -18,9 +17,9 @@ export class ReactScopeEventEmitter implements ScopeEventEmitter { const wrappedListener = (...detail: unknown[]) => { listener({}, ...detail) } - this.eventEmitter.on(eventName, wrappedListener) + this.eventEmitter.addEventListener(eventName, wrappedListener) return () => { - this.eventEmitter.off(eventName, wrappedListener) + this.eventEmitter.removeEventListener(eventName, wrappedListener) } } } diff --git a/services/web/package.json b/services/web/package.json index 8cb33b4585..f978d51550 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -344,7 +344,6 @@ "sinon-mongoose": "^2.3.0", "socket.io-mock": "^1.3.1", "storybook": "^7.4.0", - "strict-event-emitter": "^0.5.1", "terser-webpack-plugin": "^5.3.9", "thread-loader": "^4.0.2", "timekeeper": "^2.2.0",