feat(websockets): migrate WebSocket stack to data-access and react to site CRUD events#35378
feat(websockets): migrate WebSocket stack to data-access and react to site CRUD events#35378
Conversation
… site CRUD events
- Move WebSocket service from deprecated dotcms-js to libs/data-access as DotEventsSocket
using native WebSocket only (dropped long-polling — all modern browsers support WS)
- Add withWebSocket() feature to GlobalStore managing connection lifecycle, wsStatus signal,
portletLayoutUpdated and siteEvents observables
- Replace DotcmsEventsService.subscribeTo('UPDATE_PORTLET_LAYOUTS') in DotNavigationService
with globalStore.portletLayoutUpdated
- React to site CRUD events (SAVE, PUBLISH, UN_PUBLISH, UPDATE, ARCHIVE, UN_ARCHIVE, DELETE)
in DotSiteComponent: debounce list refresh, switch to default site when selected site
becomes unavailable (archived, stopped, deleted)
- Add UN_PUBLISH_SITE to SystemEventType so unpublishHost DWR fires the WebSocket event
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix timer subscription leak in scheduleReconnect() — store subscription in reconnectTimer and cancel on destroy() - Guard openSocket() against concurrent calls when socket is CONNECTING - Remove dead getLongPollingURL() method from DotEventsSocketURL - Update spec mock to remove getLongPollingURL reference - Remove invalid test in with-websocket.feature.spec.ts that assumed rxMethod would propagate errors and set wsStatus to closed (rxMethod swallows errors and connect() completes immediately, never throws) - Add dot-events-socket.service.spec.ts (was untracked) - Fix stale JSDoc in refreshSelectedSite() — "archive/un-archive" → "unavailable events" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the siteListRefresh$ Subject + two subscriptions pattern with a single pipe using tap + debounceTime directly on the merged site events observable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t mock - Add 'WebSocket site events' describe block to dot-site.component.spec.ts covering: debounced list refresh, re-fetch on update, no-op for other sites, switch-to-default on ARCHIVE/UN_PUBLISH/DELETE, error fallbacks, and unsubscribe on destroy - Add mockProvider(DotEventsSocket) to dot-site.component.spec.ts factories - Fix dot-theme.component.spec.ts: add mockProvider(DotEventsSocket) since DotThemeComponent embeds DotSiteComponent which now injects DotEventsSocket Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ing token The class existed only to build a ws/wss URL from hostname + SSL flag. Replace with InjectionToken<string> — the factory in providers.ts returns the URL string directly, and DotEventsSocket injects it as a plain string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion.host Use window.location.host (hostname + port combined) instead of manually concatenating hostname and port separately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DotEventsSocket now derives its URL directly from window.location — no injection token or external configuration needed. Removes DOT_EVENTS_SOCKET_URL token, DotEventsSocketURL file, and the factory in providers.ts entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add switchSiteEvent$() to withWebSocket feature — emits the new DotSite from the SWITCH_SITE payload (no HTTP call needed) - DotToolbarComponent now uses globalStore.siteDetails as $currentSite (signal) instead of a one-shot getCurrentSite() observable - On SWITCH_SITE: call setCurrentSite() and navigate away from edit page - Remove stale ARCHIVE_SITE handler and DotcmsEventsService dependency (archive is already handled by DotSiteComponent directly) - Update toolbar spec: add SWITCH_SITE tests, remove dotcms-js imports Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The toolbar was acting as a mediator — calling DotSiteService directly and then writing back to the store it was reading from. Move that responsibility where it belongs: - Add switchCurrentSite(identifier) to GlobalStore: calls switchSite() → getCurrentSite() → patchState internally - Handle SWITCH_SITE WebSocket event inside store onInit instead of the toolbar, removing the need for setCurrentSite() entirely - DotToolbarComponent now delegates to globalStore.switchCurrentSite() and only owns the navigation side effect (goToSiteBrowser) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ionEffect Navigation away from the edit page on site switch is an app-level concern, not a toolbar concern. Move it into a dedicated effect service that lives for the full application lifetime: - Add DotSiteNavigationEffect: subscribes to switchSiteEvent$() and calls goToSiteBrowser() when on edit page - Register it eagerly via providers.ts (providedIn root + listed) - DotToolbarComponent is now a pure UI component with no lifecycle hooks - Add DotSiteNavigationEffect.spec.ts covering both navigation paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…teNavigationEffect DotSiteNavigationEffect already handles goToSiteBrowser() when a SWITCH_SITE WebSocket event fires, which includes user-initiated switches from the toolbar. Removing the duplicated check keeps navigation responsibility in one place. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…obalStore.switchSiteEvent$ Replace all usages of the legacy dotcms-js SiteService.switchSite$ observable in apps/dotcms-ui with GlobalStore.switchSiteEvent$, which is backed by the WebSocket SWITCH_SITE event. This consolidates site-switch reactivity through the global store. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrate all subscribeTo/subscribeToEvents call sites to the new DotEventsSocket.on<T>() API. Also fixes a memory leak in DotToolbarNotificationsComponent by adding takeUntilDestroyed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wire switchSiteEvent$ mock before service creation in DotSiteNavigationEffect spec so the constructor subscription does not receive undefined - Fix GlobalStore SWITCH_SITE test: move mock setup into beforeEach before createService so onInit subscribes to the correct subject - Remove goToSiteBrowser assertion from toolbar test — navigation now lives in DotSiteNavigationEffect, not the toolbar component - Fix misleading onclose comment: 1001 is going-away (tab close), not 1000 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace SiteService.switchSite$ with GlobalStore.switchSiteEvent$() in the component test, matching the production code change on this branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-switch Replace SiteService.switchSite$ with GlobalStore.switchSiteEvent$() in the component test, matching the production code change on this branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d DotEventsSocket teardown error The real GlobalStore includes withWebSocket() whose onDestroy calls inject(DotEventsSocket) outside an injection context during test cleanup. DotSubNavComponent doesn't use GlobalStore at all, so mocking it is correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vent NG0203 in tests `inject()` called as a default parameter in `onDestroy` fires during R3Injector.destroy(), after the injection context is gone. Capture the DotEventsSocket reference in withMethods() via a new destroySocket() method and call that from onDestroy() instead. Also adds DotEventsSocket mock to dot-navigation.service.spec.ts so GlobalStore can initialize withWebSocket without opening a real socket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move createServiceFactory to describe level (Spectator requirement) and override GlobalStore per-test via createService() options so each test gets a fresh Subject for switchSiteEvent$. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…itch Replace SiteService.switchSite$ with GlobalStore.switchSiteEvent$() in the component test, matching the production code change on this branch. Update site-switch assertion to expect paginatorService.getFirstPage() instead of get() since getContainersByHost calls getFirstPage(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Template was still referencing the old public `globalStore.siteDetails()` property after the store was made private. Update to use the `$currentSite` signal alias exposed on the component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ockWebSocket static constants - store.spec.ts: move createServiceFactory to describe level to avoid Spectator "Hooks cannot be defined inside tests" error - dot-events-socket.service.spec.ts: add static CONNECTING/OPEN/CLOSING/CLOSED constants to MockWebSocket so the guard in openSocket() doesn't false-match when global.WebSocket is replaced with the mock (undefined === undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ing Math.random Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uracy, and mock cleanup - container-list: pipe switchSiteEvent$ through takeUntil(destroy$) to prevent subscription leak on destroy - providers: use provideAppInitializer to force DotSiteNavigationEffect instantiation at startup - dot-site-navigation.effect.spec: remove redundant describe-level mock; clarify beforeEach ordering - dot-events-socket.service: clarify onclose comment (1001 vs 1000 distinction) - dot-events-socket.service.spec: rename test to match what it actually asserts - store.spec: add comment explaining beforeEach ordering; fix switchSite mock type - dot-site.component.spec: restore DotEventsSocket per-event subject wiring (was commented out) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… dual WebSocket connections Migrates the last 4 consumers to use DotEventsSocket from @dotcms/data-access or GlobalStore event streams, then deletes the entire legacy WebSocket layer. - LoginService: SESSION_DESTROYED/SESSION_LOGOUT now wired to DotEventsSocket directly - RoutingService: UPDATE_PORTLET_LAYOUTS now wired to DotEventsSocket (avoids circular dep) - SiteService: event subscriptions removed (data API unchanged); deprecated class kept - DotPluginsListStore: OSGI events migrated from DotcmsEventsService to DotEventsSocket - Removed DotcmsEventsService, DotEventsSocket (legacy), DotEventsSocketURL from all provider arrays - Deleted: dotcms-events.service.ts, dot-event-socket.ts, long-polling-protocol.ts, websockets-protocol.ts, dot-event-socket-url model, and all related spec files - Cleaned barrel exports in dotcms-js/src/public_api.ts - Updated ~60 spec files to remove dead provider mocks for the deleted services Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tsSocket import Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t import dotcms-js → data-access is a known architectural constraint; the circular dep lint rule fires because data-access re-exports symbols that import back into dotcms-js. Suppress on these two lines only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
❌ Issue Linking RequiredThis PR could not be linked to an issue. All PRs must be linked to an issue for tracking purposes. How to fix this:Option 1: Add keyword to PR body (Recommended - auto-removes this comment)
Why is this required?Issue linking ensures proper tracking, documentation, and helps maintain project history. It connects your code changes to the problem they solve.--- This comment was automatically generated by the issue linking workflow |
|
Claude finished @fmontes's task in 2m 37s —— View job PR ReviewLarge, well-structured migration. The core WebSocket architecture is sound. Issues below are ordered by severity. 🔴
|
b4312a9 to
4619987
Compare
…nd fix TypeScript type in withWebSocket - Add DotcmsEventsService to public_api.ts so @dotcms/dotcms-js resolves it - Explicit type annotation on dotcmsEventsService inject() call to satisfy strict TS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Migrates the admin UI’s system-events WebSocket integration from the legacy @dotcms/dotcms-js stack to a new native DotEventsSocket in @dotcms/data-access, then wires app behavior (site CRUD + navigation/menu refresh) off typed socket streams and GlobalStore helpers to ensure only one /api/ws/v1/system/events connection is active.
Changes:
- Added a new native WebSocket client (
DotEventsSocket) in@dotcms/data-accesswith reconnection + typedon<T>()API. - Introduced
withWebSocket()GlobalStore feature to own socket lifecycle + expose common event streams (switchSiteEvent$,portletLayoutUpdated$, etc.). - Removed legacy WebSocket/long-polling protocol layer and updated app/services/specs to use the new socket/global-store streams; added missing backend
UN_PUBLISH_SITEenum.
Reviewed changes
Copilot reviewed 93 out of 93 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| dotCMS/src/main/java/com/dotcms/api/system/event/SystemEventType.java | Adds missing site stop/unpublish system event enum. |
| core-web/libs/utils-testing/src/lib/dotcms-events-service.mock.ts | Removes legacy events-service mock. |
| core-web/libs/utils-testing/src/index.ts | Stops exporting removed legacy events-service mock. |
| core-web/libs/ui/src/lib/components/dot-theme/dot-theme.component.spec.ts | Updates tests to mock DotEventsSocket instead of legacy events service. |
| core-web/libs/ui/src/lib/components/dot-site/dot-site.component.ts | Switches site selector refresh logic to DotEventsSocket and handles unavailable-site events. |
| core-web/libs/ui/src/lib/components/dot-site/dot-site.component.spec.ts | Adds coverage for site WebSocket event reactions using DotEventsSocket mocks. |
| core-web/libs/template-builder/src/lib/components/template-builder/template-builder.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/libs/template-builder/src/lib/components/template-builder/components/template-builder-actions/template-builder-actions.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-ema-workflow-actions/dot-ema-workflow-actions.service.spec.ts | Removes legacy socket URL factory/provider from tests. |
| core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/libs/portlets/dot-plugins/src/lib/dot-plugins-list/store/dot-plugins-list.store.ts | Migrates OSGi event subscriptions to DotEventsSocket.on(). |
| core-web/libs/portlets/dot-plugins/src/lib/dot-plugins-list/store/dot-plugins-list.store.spec.ts | Updates store tests to mock DotEventsSocket.on(). |
| core-web/libs/portlets/dot-plugins/src/lib/dot-plugins-list/dot-plugins-list.component.spec.ts | Updates component tests to mock DotEventsSocket. |
| core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-list/dot-experiments-list.component.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/libs/global-store/src/lib/store.ts | Adds withWebSocket() feature and new switchCurrentSite() method; syncs site on SWITCH_SITE events. |
| core-web/libs/global-store/src/lib/store.spec.ts | Updates tests for new switchCurrentSite() and SWITCH_SITE socket syncing. |
| core-web/libs/global-store/src/lib/features/with-websocket/with-websocket.feature.ts | New feature to start/destroy socket and expose common event observables. |
| core-web/libs/global-store/src/lib/features/with-websocket/with-websocket.feature.spec.ts | Adds tests for withWebSocket() lifecycle + status signal updates. |
| core-web/libs/global-store/src/index.ts | Re-exports WebSocketStatus for consumers. |
| core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts | Updates GlobalStore mock API to remove setCurrentSite. |
| core-web/libs/dotcms-js/src/public_api.ts | Removes exports for legacy websocket stack types. |
| core-web/libs/dotcms-js/src/lib/core/util/websockets-protocol.ts | Deletes legacy WebSocket protocol implementation. |
| core-web/libs/dotcms-js/src/lib/core/util/websockets-protocol.spec.ts | Deletes tests for legacy WebSocket protocol. |
| core-web/libs/dotcms-js/src/lib/core/util/models/dot-event-socket-url.ts | Deletes legacy socket URL model. |
| core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.ts | Deletes legacy long-polling protocol. |
| core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.spec.ts | Deletes tests for legacy long-polling protocol. |
| core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.ts | Deletes legacy event socket implementation. |
| core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.spec.ts | Deletes tests for legacy event socket. |
| core-web/libs/dotcms-js/src/lib/core/site.service.ts | Removes websocket-based site event wiring from legacy SiteService. |
| core-web/libs/dotcms-js/src/lib/core/login.service.ts | Keeps legacy event subscriptions but removes socket start side-effect. |
| core-web/libs/dotcms-js/src/lib/core/dotcms-events.service.ts | Refactors to a deprecated Subject-only bus fed externally (no socket logic). |
| core-web/libs/dot-rules/src/lib/rule-engine.module.ts | Removes legacy events service from module providers. |
| core-web/libs/data-access/src/lib/dot-workflow-event-handler/dot-workflow-event-handler.service.spec.ts | Removes legacy socket URL factory/provider from tests. |
| core-web/libs/data-access/src/lib/dot-websocket/dot-events-socket.service.ts | Adds new native WebSocket client with reconnect + status stream. |
| core-web/libs/data-access/src/lib/dot-websocket/dot-events-socket.service.spec.ts | Adds tests for DotEventsSocket connection, filtering, and reconnection behavior. |
| core-web/libs/data-access/src/lib/dot-websocket/dot-event-message.model.ts | Adds typed model for raw system-event messages. |
| core-web/libs/data-access/src/lib/dot-message-display/dot-message-display.service.ts | Migrates MESSAGE handling to DotEventsSocket.on(). |
| core-web/libs/data-access/src/lib/dot-message-display/dot-message-display.service.spec.ts | Updates tests to emit messages through DotEventsSocket mock. |
| core-web/libs/data-access/src/index.ts | Exposes new websocket services/models from @dotcms/data-access. |
| core-web/apps/dotcms-ui/src/app/view/components/main-legacy/main-legacy.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-workflow-task-detail/dot-workflow-task-detail.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.spec.ts | Updates toolbar tests for GlobalStore site switching and new navigation effect wiring. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.component.ts | Simplifies site switching to GlobalStore.switchCurrentSite() and removes legacy event wiring. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.component.html | Binds site selector value via $currentSite() signal. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/store/dot-toolbar-user.store.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-notifications/dot-toolbar-notifications.component.ts | Migrates NOTIFICATION subscription to DotEventsSocket.on() and bounds list size. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-notifications/components/dot-notification-item/dot-notification-item.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-persona-selector/dot-persona-selector.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-navigation/services/dot-navigation.service.ts | Moves UPDATE_PORTLET_LAYOUTS handling to GlobalStore portletLayoutUpdated$(). |
| core-web/apps/dotcms-ui/src/app/view/components/dot-navigation/services/dot-navigation.service.spec.ts | Removes legacy events service mock and updates to new GlobalStore wiring. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-navigation/components/dot-sub-nav/dot-sub-nav.component.spec.ts | Adjusts GlobalStore provisioning in tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-large-message-display/dot-large-message-display.component.ts | Migrates LARGE_MESSAGE subscription to DotEventsSocket.on(). |
| core-web/apps/dotcms-ui/src/app/view/components/dot-large-message-display/dot-large-message-display.component.spec.ts | Updates tests to drive LARGE_MESSAGE via DotEventsSocket mock. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-iframe-dialog/dot-iframe-dialog.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/components/dot-reorder-menu/dot-reorder-menu.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/components/dot-create-contentlet/dot-create-contentlet.component.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/components/dot-contentlet-wrapper/dot-contentlet-wrapper.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/components/dot-add-contentlet/dot-add-contentlet.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-add-persona-dialog/dot-add-persona-dialog.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-porlet-legacy/iframe-porlet-legacy.component.ts | Switches site-change reaction to GlobalStore and AI prompt to DotEventsSocket. |
| core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-porlet-legacy/iframe-porlet-legacy.component.spec.ts | Updates tests to provide DotEventsSocket from data-access. |
| core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-component/iframe.component.ts | Replaces legacy multi-event subscription with merged DotEventsSocket.on() streams. |
| core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-component/iframe.component.spec.ts | Updates tests to drive iframe reload events via DotEventsSocket mock subjects. |
| core-web/apps/dotcms-ui/src/app/view/components/_common/dot-wizard/dot-wizard.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/test/dot-test-bed.ts | Removes legacy socket URL factory and legacy websocket providers from shared test bed. |
| core-web/apps/dotcms-ui/src/app/shared/shared.module.ts | Removes legacy websocket providers from shared module. |
| core-web/apps/dotcms-ui/src/app/providers.ts | Removes legacy websocket providers; eagerly provides DotSiteNavigationEffect. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-listing/components/dot-content-type-copy-dialog/dot-content-type-copy-dialog.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/form/content-types-form.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-drop-zone/content-type-fields-drop-zone.component.spec.ts | Removes legacy DotEventsSocket (dotcms-js) from test providers. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-list/dot-template-list.component.ts | Switches site-change listener to GlobalStore switchSiteEvent$(). |
| core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-list/dot-template-list.component.spec.ts | Updates tests to trigger site changes via GlobalStore stream. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-props/dot-template-props.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.ts | Switches site-change listener to GlobalStore switchSiteEvent$(). |
| core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.spec.ts | Updates tests to trigger site changes via GlobalStore stream. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-porlet-detail/dot-workflow-task/dot-workflow-task.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-porlet-detail/dot-portlet-detail.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-porlet-detail/dot-contentlets/dot-contentlets.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-pages/dot-pages-table/dot-pages-table.component.spec.ts | Removes legacy events service provider usage in tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-pages/dot-pages-store/dot-pages.store.spec.ts | Removes legacy events service mock/provider from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-properties/dot-container-properties.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-create.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-code/dot-add-variable/dot-add-variable.component.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcms-ui/src/app/portlets/dot-containers/container-list/container-list.component.ts | Switches site-change listener to GlobalStore switchSiteEvent$(). |
| core-web/apps/dotcms-ui/src/app/portlets/dot-containers/container-list/container-list.component.spec.ts | Updates tests to trigger site changes via GlobalStore stream. |
| core-web/apps/dotcms-ui/src/app/api/services/dot-site-navigation/dot-site-navigation.effect.ts | Adds app-level effect to navigate away from edit page on SWITCH_SITE events. |
| core-web/apps/dotcms-ui/src/app/api/services/dot-site-navigation/dot-site-navigation.effect.spec.ts | Adds tests for edit-page navigation behavior on SWITCH_SITE. |
| core-web/apps/dotcms-ui/src/app/api/services/dot-custom-event-handler/dot-custom-event-handler.service.spec.ts | Removes legacy events socket/providers from tests. |
| core-web/apps/dotcdn/src/app/app.module.ts | Removes legacy websocket providers from dotcdn app module. |
| connect(): Observable<void> { | ||
| return new Observable<void>((subscriber) => { | ||
| this.destroyed = false; | ||
| this.openSocket(); | ||
| subscriber.next(); | ||
| subscriber.complete(); | ||
| }); | ||
| } | ||
|
|
||
| /** Closes the connection permanently (e.g. on logout). */ | ||
| destroy(): void { | ||
| this.destroyed = true; | ||
| this.reconnectTimer?.unsubscribe(); | ||
| this.reconnectTimer = null; | ||
| this.setStatus('closed'); | ||
| this.socket?.close(); | ||
| this.socket = null; | ||
| } | ||
|
|
||
| /** Emits the typed payload of a specific event type. */ | ||
| on<T>(eventType: string): Observable<T> { | ||
| return this._message.asObservable().pipe( | ||
| filter((msg) => msg.event === eventType), | ||
| map((msg) => msg.payload?.data as T) | ||
| ); | ||
| } | ||
|
|
||
| /** All raw messages from the server. */ | ||
| messages(): Observable<DotEventMessage> { | ||
| return this._message.asObservable(); | ||
| } | ||
|
|
||
| isConnected(): boolean { | ||
| return this.status === 'connected'; | ||
| } | ||
|
|
||
| /** Emits only when the status actually changes. */ | ||
| status$(): Observable<WebSocketStatus> { | ||
| return this._status.asObservable().pipe(distinctUntilChanged()); | ||
| } | ||
|
|
||
| private openSocket(): void { | ||
| if (this.destroyed || this.socket?.readyState === WebSocket.CONNECTING) { | ||
| return; | ||
| } | ||
|
|
||
| this.setStatus(this.retryCount === 0 ? 'connecting' : 'reconnecting'); | ||
|
|
||
| try { | ||
| this.socket = new WebSocket(this.socketURL); | ||
| } catch { |
There was a problem hiding this comment.
Fixed in c62d7be on branch claude/review-pr-comments-4tBxM. openSocket() now early-returns when readyState is CONNECTING or OPEN, and connect() cancels any pending reconnect timer before re-opening. CLOSING still falls through and onclose → scheduleReconnect handles it.
Generated by Claude Code
| const tagEvent = (event: string) => | ||
| this.eventsSocket | ||
| .on<{ identifier: string }>(event) | ||
| .pipe(map((data) => ({ ...data, event }))); | ||
|
|
There was a problem hiding this comment.
Not fixing. {...undefined} is a no-op spread in ES2018+ — it doesn't throw, it produces {}. The resulting object { event: 'SAVE_SITE' } is then handled safely downstream in refreshSelectedSite which already uses siteData?.identifier !== pinned.identifier (optional chaining) and early-returns on mismatch. So an unexpected payload shape just becomes a no-op, not a crash.
The underlying type concern (payload not always present) is addressed separately by making DotEventMessage.payload optional in c62d7be.
Generated by Claude Code
| onInit(store) { | ||
| // Load current site on store initialization | ||
| // System configuration is automatically loaded by withSystem feature | ||
| store.loadCurrentSite(); | ||
| // Keep siteDetails in sync when another user/tab switches the site | ||
| store | ||
| .switchSiteEvent$() | ||
| .pipe(tap((site) => patchState(store, { siteDetails: site }))) | ||
| .subscribe(); |
There was a problem hiding this comment.
Fixed in c62d7be. Converted the bare .subscribe() in GlobalStore.onInit into a syncSiteOnSwitchEvent rxMethod (same pattern as loadCurrentSite / trackStatus). Lifetime is now tied to the store — no manual teardown needed, and it's cleaned up between test module teardowns.
Generated by Claude Code
| subscribeToEvents<T>(clientEventTypes: string[]): Observable<DotEventTypeWrapper<T>> { | ||
| const subject: Subject<DotEventTypeWrapper<T>> = new Subject<DotEventTypeWrapper<T>>(); | ||
| const subject = new Subject<DotEventTypeWrapper<T>>(); | ||
|
|
||
| clientEventTypes.forEach((eventType: string) => { | ||
| this.subscribeTo(eventType).subscribe((data: T) => { | ||
| subject.next({ | ||
| data: data, | ||
| name: eventType | ||
| }); | ||
| clientEventTypes.forEach((eventType) => { | ||
| this.subscribeTo<T>(eventType).subscribe((data) => { | ||
| subject.next({ data, name: eventType }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Not fixing in this PR. DotcmsEventsService is already marked @deprecated in this PR, and its only remaining consumers (LoginService, SiteService) are root-scoped / app-lifetime — the inner subscriptions don't leak in practice. Fully migrating LoginService off the legacy bus is explicit follow-up work per the PR description. Leaving as-is; worth tracking as a separate cleanup ticket.
Generated by Claude Code
…pilot - DotEventsSocket: guard OPEN state in openSocket() and cancel pending reconnect timer on manual connect() so repeat calls can never spawn a second concurrent WebSocket (Copilot, Claude #4) - dot-site.component: on switchSite(null) or getSiteById error, clear the CVA value via onSiteChange(null) instead of only nulling pinnedOption, so parent forms see the null value (Claude #1) - DotEventMessage.payload: make payload and payload.data optional to match the defensive handling already in on<T>() (Claude #7) - GlobalStore.onInit: replace bare .subscribe() for switchSiteEvent$() with a lifecycle-tracked rxMethod so the subscription is torn down with the store (Claude #3, Copilot) - DotEventsSocket.messages(): document as @internal; consumers should use the typed on<T>() API (Claude #8) https://claude.ai/code/session_01TYiVFjVWzBxdAnrJFZJv66
Review feedback triage — Claude + CopilotReviewed all comments from both bots. Fixes pushed to branch ✅ Fixed
❌ Not fixing (out of scope / not a real issue)
Verification
Generated by Claude Code |
…pilot - DotEventsSocket: guard OPEN state in openSocket() and cancel pending reconnect timer on manual connect() so repeat calls can never spawn a second concurrent WebSocket (Copilot, Claude #4) - dot-site.component: on switchSite(null) or getSiteById error, clear the CVA value via onSiteChange(null) instead of only nulling pinnedOption, so parent forms see the null value (Claude #1) - DotEventMessage.payload: make payload and payload.data optional to match the defensive handling already in on<T>() (Claude #7) - GlobalStore.onInit: replace bare .subscribe() for switchSiteEvent$() with a lifecycle-tracked rxMethod so the subscription is torn down with the store (Claude #3, Copilot) - DotEventsSocket.messages(): document as @internal; consumers should use the typed on<T>() API (Claude #8) https://claude.ai/code/session_01TYiVFjVWzBxdAnrJFZJv66
|
Fixes from the triage above are now pushed directly to Generated by Claude Code |
TL;DR
Builds a new native WebSocket stack in
@dotcms/data-access, wires it into the app to react to site CRUD events and toolbar navigation, then removes the entire legacyDotcmsEventsService/dotcms-jsWebSocket layer — so only one connection to/api/ws/v1/system/eventsever runs.What changed
New WebSocket stack (
libs/data-access+libs/global-store)DotEventsSocket— native WebSocket only (long-polling dropped; all modern browsers support WS), exponential backoff with jitter, typedon<T>(eventType)API, reactivestatus$()streamwithWebSocket()NgRx signal store feature — manages connection lifecycle at app init viaprovideAppInitializer, exposeswsStatussignal and typed event observables:switchSiteEvent$,portletLayoutUpdated$,siteEvents$Site CRUD reactions
DotSiteComponentdebounces list refresh on any site WebSocket event; auto-switches to default site when the selected site is archived, stopped, or deletedSystemEventType.UN_PUBLISH_SITEadded — was missing, causing stop-site events to be silently swallowedSWITCH_SITEevent viaDotSiteNavigationEffect(extracted from toolbar component)Migration of all
DotcmsEventsServiceconsumersReplaced
subscribeTo()/subscribeToEvents()withDotEventsSocket.on<T>()orGlobalStoreevent streams in:DotMessageDisplayServiceDotToolbarNotificationsComponent(also fixed missingtakeUntilDestroyedmemory leak)DotLargeMessageDisplayComponentIframeComponent/IframePortletLegacyComponentLoginService—SESSION_DESTROYED/SESSION_LOGOUTRoutingService—UPDATE_PORTLET_LAYOUTSDotPluginsListStore—OSGI_FRAMEWORK_RESTART/OSGI_BUNDLES_LOADED/OSGI_BUNDLES_UPLOAD_FAILEDSiteService— event subscriptions removed (data API and class kept;@deprecated)Deleted — entire legacy WebSocket layer
DotcmsEventsServiceand itssubscribeTo()/subscribeToEvents()APIDotEventsSocket(dotcms-js version),DotEventsSocketURLinjection token,dotEventSocketURLFactoryWebSocketProtocol,LongPollingProtocol,Protocol,DotWebSocketConfigDotcmsEventsServiceMockfrom@dotcms/utils-testing@dotcms/dotcms-jsTest plan
UPDATE_PORTLET_LAYOUTSeventyarn nx test data-accesspassesyarn nx test global-storepassesyarn nx test portlets-dot-plugins-portletpasses🤖 Generated with Claude Code