diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 01dd82bb3d..774ba5c0be 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -719,6 +719,7 @@ const _ProjectController = { isTokenMember, isInvitedMember ), + chatEnabled: Features.hasFeature('chat'), roMirrorOnClientNoLocalStorage: Settings.adminOnlyLogin || project.name.startsWith('Debug: '), languages: Settings.languages, diff --git a/services/web/app/src/infrastructure/Features.js b/services/web/app/src/infrastructure/Features.js index c449b263a8..066d2e4305 100644 --- a/services/web/app/src/infrastructure/Features.js +++ b/services/web/app/src/infrastructure/Features.js @@ -58,6 +58,8 @@ const Features = { ) case 'registration': return Boolean(Settings.overleaf) + case 'chat': + return Boolean(Settings.disableChat) === false case 'github-sync': return Boolean(Settings.enableGithubSync) case 'git-bridge': diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index 733118ea2a..8fefea1a10 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -1139,19 +1139,21 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { SpellingController.unlearn ) - webRouter.get( - '/project/:project_id/messages', - AuthorizationMiddleware.blockRestrictedUserFromProject, - AuthorizationMiddleware.ensureUserCanReadProject, - ChatController.getMessages - ) - webRouter.post( - '/project/:project_id/messages', - AuthorizationMiddleware.blockRestrictedUserFromProject, - AuthorizationMiddleware.ensureUserCanReadProject, - RateLimiterMiddleware.rateLimit(rateLimiters.sendChatMessage), - ChatController.sendMessage - ) + if (Features.hasFeature('chat')) { + webRouter.get( + '/project/:project_id/messages', + AuthorizationMiddleware.blockRestrictedUserFromProject, + AuthorizationMiddleware.ensureUserCanReadProject, + ChatController.getMessages + ) + webRouter.post( + '/project/:project_id/messages', + AuthorizationMiddleware.blockRestrictedUserFromProject, + AuthorizationMiddleware.ensureUserCanReadProject, + RateLimiterMiddleware.rateLimit(rateLimiters.sendChatMessage), + ChatController.sendMessage + ) + } webRouter.post( '/project/:Project_id/references/indexAll', diff --git a/services/web/app/views/project/editor/meta.pug b/services/web/app/views/project/editor/meta.pug index 171d424b87..712deffc61 100644 --- a/services/web/app/views/project/editor/meta.pug +++ b/services/web/app/views/project/editor/meta.pug @@ -9,6 +9,7 @@ meta(name="ol-isTokenMember" data-type="boolean" content=isTokenMember) meta(name="ol-isRestrictedTokenMember" data-type="boolean" content=isRestrictedTokenMember) meta(name="ol-maxDocLength" data-type="json" content=maxDocLength) meta(name="ol-wikiEnabled" data-type="boolean" content=settings.proxyLearn) +meta(name="ol-chatEnabled" data-type="boolean" content=chatEnabled) meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl) meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled) meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain) diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index fed30c8a00..ef209f4f34 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -407,6 +407,7 @@ module.exports = { }, ], + disableChat: process.env.OVERLEAF_DISABLE_CHAT === 'true', enableSubscriptions: false, restrictedCountries: [], enableOnboardingEmails: process.env.ENABLE_ONBOARDING_EMAILS === 'true', diff --git a/services/web/frontend/js/features/chat/context/chat-context.tsx b/services/web/frontend/js/features/chat/context/chat-context.tsx index 65e6093c7c..715c178d22 100644 --- a/services/web/frontend/js/features/chat/context/chat-context.tsx +++ b/services/web/frontend/js/features/chat/context/chat-context.tsx @@ -17,6 +17,8 @@ import { appendMessage, prependMessages } from '../utils/message-list-appender' import useBrowserWindow from '../../../shared/hooks/use-browser-window' import { useLayoutContext } from '../../../shared/context/layout-context' import { useIdeContext } from '@/shared/context/ide-context' +import getMeta from '@/utils/meta' +import { debugConsole } from '@/utils/debugging' const PAGE_SIZE = 50 @@ -187,6 +189,8 @@ export const ChatContext = createContext< >(undefined) export const ChatProvider: FC = ({ children }) => { + const chatEnabled = getMeta('ol-chatEnabled') + const clientId = useRef() if (clientId.current === undefined) { clientId.current = chatClientIdGenerator.generate() @@ -235,6 +239,10 @@ export const ChatProvider: FC = ({ children }) => { } function loadInitialMessages() { + if (!chatEnabled) { + debugConsole.warn(`chat is disabled, won't load initial messages`) + return + } if (state.initialMessagesLoaded) return dispatch({ type: 'INITIAL_FETCH_MESSAGES' }) @@ -242,11 +250,19 @@ export const ChatProvider: FC = ({ children }) => { } function loadMoreMessages() { + if (!chatEnabled) { + debugConsole.warn(`chat is disabled, won't load messages`) + return + } dispatch({ type: 'FETCH_MESSAGES' }) fetchMessages() } function reset() { + if (!chatEnabled) { + debugConsole.warn(`chat is disabled, won't reset chat`) + return + } dispatch({ type: 'CLEAR' }) fetchMessages() } @@ -256,10 +272,20 @@ export const ChatProvider: FC = ({ children }) => { loadMoreMessages, reset, } - }, [projectId, state.atEnd, state.initialMessagesLoaded, state.lastTimestamp]) + }, [ + chatEnabled, + projectId, + state.atEnd, + state.initialMessagesLoaded, + state.lastTimestamp, + ]) const sendMessage = useCallback( content => { + if (!chatEnabled) { + debugConsole.warn(`chat is disabled, won't send message`) + return + } if (!content) return dispatch({ @@ -278,17 +304,21 @@ export const ChatProvider: FC = ({ children }) => { }) }) }, - [projectId, user] + [chatEnabled, projectId, user] ) const markMessagesAsRead = useCallback(() => { + if (!chatEnabled) { + debugConsole.warn(`chat is disabled, won't mark messages as read`) + return + } dispatch({ type: 'MARK_MESSAGES_AS_READ' }) - }, []) + }, [chatEnabled]) // Handling receiving messages over the socket const { socket } = useIdeContext() useEffect(() => { - if (!socket) return + if (!chatEnabled || !socket) return function receivedMessage(message: any) { // If the message is from the current client id, then we are receiving the sent message back from the socket. @@ -304,7 +334,7 @@ export const ChatProvider: FC = ({ children }) => { socket.removeListener('new-chat-message', receivedMessage) } - }, [socket]) + }, [chatEnabled, socket]) // Handle unread messages useEffect(() => { diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx index 071d51e37c..4779c14a11 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx @@ -51,6 +51,8 @@ const ToolbarHeader = React.memo(function ToolbarHeader({ openShareModal, trackChangesVisible, }) { + const chatEnabled = getMeta('ol-chatEnabled') + const { t } = useTranslation() const shouldDisplayPublishButton = hasPublishPermissions && PublishButton @@ -123,7 +125,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({ - {chatVisible && ( + {chatEnabled && chatVisible && ( { const { view } = useLayoutContext() @@ -38,6 +39,8 @@ export const MainLayout: FC = () => { handlePaneExpand: handleChatExpand, } = useChatPane() + const chatEnabled = getMeta('ol-chatEnabled') + const { t } = useTranslation() return ( @@ -90,27 +93,31 @@ export const MainLayout: FC = () => { - + {chatEnabled && ( + <> + - {/* chat */} - - - + {/* chat */} + + + + + )} diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index b9faf471fc..7ffe3ef86c 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -69,6 +69,7 @@ export interface Meta { 'ol-cannot-link-other-third-party-sso': boolean 'ol-cannot-reactivate-subscription': boolean 'ol-cannot-use-ai': boolean + 'ol-chatEnabled': boolean 'ol-countryCode': PricingFormState['country'] 'ol-couponCode': PricingFormState['coupon'] 'ol-createdAt': Date diff --git a/services/web/test/frontend/features/chat/components/chat-pane.test.jsx b/services/web/test/frontend/features/chat/components/chat-pane.test.jsx index fa6170e623..5caf51097e 100644 --- a/services/web/test/frontend/features/chat/components/chat-pane.test.jsx +++ b/services/web/test/frontend/features/chat/components/chat-pane.test.jsx @@ -22,6 +22,7 @@ describe('', function () { beforeEach(function () { window.metaAttributesCache.set('ol-user', user) + window.metaAttributesCache.set('ol-chatEnabled', true) }) afterEach(function () { diff --git a/services/web/test/frontend/features/chat/context/chat-context.test.jsx b/services/web/test/frontend/features/chat/context/chat-context.test.jsx index ea4b25f63f..76410a65fd 100644 --- a/services/web/test/frontend/features/chat/context/chat-context.test.jsx +++ b/services/web/test/frontend/features/chat/context/chat-context.test.jsx @@ -29,6 +29,7 @@ describe('ChatContext', function () { stubMathJax() window.metaAttributesCache.set('ol-user', user) + window.metaAttributesCache.set('ol-chatEnabled', true) this.stub = sinon.stub(chatClientIdGenerator, 'generate').returns(uuidValue) }) diff --git a/services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.jsx b/services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.jsx index f01e317993..f8441aae56 100644 --- a/services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.jsx +++ b/services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.jsx @@ -26,6 +26,10 @@ describe('', function () { detach: () => {}, } + beforeEach(function () { + window.metaAttributesCache.set('ol-chatEnabled', true) + }) + describe('cobranding logo', function () { it('is not displayed by default', function () { renderWithEditorContext()