feat: disconnect collab websocket on idle tabs (#848)

* disconnect real-time collab if user is idle
* log yjs document disconnect and unload in dev mode
* no longer set editor to read-only mode on collab websocket disconnection
* treat delayed collab websocket "connecting" state as disconnected
* increase maxDebounce to 45 seconds
* add reset handle to useIdle hook
This commit is contained in:
Philip Okugbe
2025-03-08 18:16:23 +00:00
committed by GitHub
parent dd52eb15ca
commit fd36076ae7
8 changed files with 119 additions and 12 deletions

View File

@ -13,6 +13,7 @@
"start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main",
"collab:prod": "cross-env NODE_ENV=production node dist/collaboration/server/collab-main",
"collab:dev": "cross-env NODE_ENV=development node dist/collaboration/server/collab-main",
"email:dev": "email dev -p 5019 -d ./src/integrations/transactional/emails",
"migration:create": "tsx src/database/migrate.ts create",
"migration:up": "tsx src/database/migrate.ts up",

View File

@ -11,6 +11,7 @@ import {
parseRedisUrl,
RedisConfig,
} from '../common/helpers';
import { LoggerExtension } from './extensions/logger.extension';
@Injectable()
export class CollaborationGateway {
@ -20,17 +21,19 @@ export class CollaborationGateway {
constructor(
private authenticationExtension: AuthenticationExtension,
private persistenceExtension: PersistenceExtension,
private loggerExtension: LoggerExtension,
private environmentService: EnvironmentService,
) {
this.redisConfig = parseRedisUrl(this.environmentService.getRedisUrl());
this.hocuspocus = HocuspocusServer.configure({
debounce: 10000,
maxDebounce: 20000,
maxDebounce: 45000,
unloadImmediately: false,
extensions: [
this.authenticationExtension,
this.persistenceExtension,
this.loggerExtension,
...(this.environmentService.isCollabDisableRedis()
? []
: [

View File

@ -8,12 +8,14 @@ import { IncomingMessage } from 'http';
import { WebSocket } from 'ws';
import { TokenModule } from '../core/auth/token.module';
import { HistoryListener } from './listeners/history.listener';
import { LoggerExtension } from './extensions/logger.extension';
@Module({
providers: [
CollaborationGateway,
AuthenticationExtension,
PersistenceExtension,
LoggerExtension,
HistoryListener,
],
exports: [CollaborationGateway],

View File

@ -0,0 +1,19 @@
import {
Extension,
onDisconnectPayload,
onLoadDocumentPayload,
} from '@hocuspocus/server';
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class LoggerExtension implements Extension {
private readonly logger = new Logger('Collab' + LoggerExtension.name);
async onDisconnect(data: onDisconnectPayload) {
this.logger.debug(`User disconnected from "${data.documentName}".`);
}
async afterUnloadDocument(data: onLoadDocumentPayload) {
this.logger.debug('Unloaded ' + data.documentName + ' from memory');
}
}