In 2-process deployments (COLLAB_URL set) the standalone collab process runs Hocuspocus onStoreDocument, which emits PAGE_UPDATED with a treeUpdate snapshot on a collaborative rename. But CollabAppModule has no WsModule, so PageWsListener (the broadcaster) only exists in the API process — the collab-originated tree update never reached clients, and other users' sidebars/breadcrumbs went stale. Bridge it over Redis pub/sub with the API process as the single broadcast authority: - PageTreeBridgePublisher (registered ONLY in CollabAppModule) listens for PAGE_UPDATED and, when a treeUpdate snapshot is present, publishes it to the collab:tree-update channel. Gated exactly like PageWsListener so content-only saves never publish noise. - PageTreeBridgeSubscriber (registered in WsModule, API process) subscribes on a dedicated duplicated connection and re-broadcasts each snapshot through WsTreeService.broadcastPageUpdated — the same restriction-aware emitTreeEvent path, so authorization is preserved. Double-broadcast is prevented by module placement: the publisher lives only in the standalone collab process's root module, so in single-process mode it is never loaded and the local PageWsListener stays the sole broadcaster. The bridge is optional and fail-safe: publish errors, malformed payloads, broadcast rejections, an unlistened 'error' on the subscriber connection, and a subscribe() failure at boot are all caught and logged, never crashing or blocking the process. NOTE: assumes a single API broadcaster; horizontal API scaling would need a consumer-group/leader-election instead of fan-out pub/sub. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22 lines
632 B
TypeScript
22 lines
632 B
TypeScript
import { Global, Module } from '@nestjs/common';
|
|
import { WsGateway } from './ws.gateway';
|
|
import { WsService } from './ws.service';
|
|
import { WsTreeService } from './ws-tree.service';
|
|
import { PageWsListener } from './listeners/page-ws.listener';
|
|
import { PageTreeBridgeSubscriber } from './listeners/page-tree-bridge.subscriber';
|
|
import { TokenModule } from '../core/auth/token.module';
|
|
|
|
@Global()
|
|
@Module({
|
|
imports: [TokenModule],
|
|
providers: [
|
|
WsGateway,
|
|
WsService,
|
|
WsTreeService,
|
|
PageWsListener,
|
|
PageTreeBridgeSubscriber,
|
|
],
|
|
exports: [WsGateway, WsService, WsTreeService],
|
|
})
|
|
export class WsModule {}
|