refactor: Remove unused stream-notifier system

The direct notification system wasn't being used since hasListeners()
was checking before any listeners were set up. All sessions were using
file watching anyway. Simplified the code by removing the unused
notification system and keeping only the optimized file watching.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mario Zechner 2025-06-22 21:09:57 +02:00
parent 312786230b
commit 1bb15097a4
3 changed files with 35 additions and 150 deletions

View file

@ -8,7 +8,6 @@
import * as fs from 'fs';
import * as path from 'path';
import { AsciinemaHeader, AsciinemaEvent, PtyError } from './types.js';
import { streamNotifier } from '../services/stream-notifier.js';
export class AsciinemaWriter {
private writeStream: fs.WriteStream;
@ -16,17 +15,11 @@ export class AsciinemaWriter {
private utf8Buffer: Buffer = Buffer.alloc(0);
private headerWritten = false;
private fd: number | null = null;
private sessionId: string;
constructor(
private filePath: string,
private header: AsciinemaHeader
) {
// Extract session ID from file path
const pathParts = this.filePath.split(path.sep);
const controlIndex = pathParts.indexOf('control');
this.sessionId =
controlIndex >= 0 && pathParts[controlIndex + 1] ? pathParts[controlIndex + 1] : 'unknown';
this.startTime = new Date();
// Ensure directory exists
@ -83,11 +76,6 @@ export class AsciinemaWriter {
const headerJson = JSON.stringify(this.header);
this.writeStream.write(headerJson + '\n');
this.headerWritten = true;
// Notify about header
if (streamNotifier.hasListeners(this.sessionId)) {
streamNotifier.notifyStreamUpdate(this.sessionId, headerJson + '\n');
}
}
/**
@ -160,11 +148,6 @@ export class AsciinemaWriter {
writeRawJson(jsonValue: unknown): void {
const jsonString = JSON.stringify(jsonValue);
this.writeStream.write(jsonString + '\n');
// Direct notification for exit events
if (streamNotifier.hasListeners(this.sessionId)) {
streamNotifier.notifyStreamUpdate(this.sessionId, jsonString + '\n');
}
}
/**
@ -176,13 +159,7 @@ export class AsciinemaWriter {
const eventJson = JSON.stringify(eventArray);
this.writeStream.write(eventJson + '\n');
// Direct notification for lowest latency
if (streamNotifier.hasListeners(this.sessionId)) {
streamNotifier.notifyStreamUpdate(this.sessionId, eventJson + '\n');
}
// Force immediate disk write to trigger file watchers
// This is critical for real-time performance with forwarded sessions
if (this.fd !== null) {
try {
fs.fsyncSync(this.fd);

View file

@ -1,64 +0,0 @@
import { EventEmitter } from 'events';
interface StreamUpdate {
sessionId: string;
data: string;
timestamp: number;
}
/**
* Global event emitter for direct in-process notifications between
* AsciinemaWriter and StreamWatcher to bypass file watching latency
*/
export class StreamNotifier extends EventEmitter {
// Define event types for type safety
on(event: 'stream-update', listener: (update: StreamUpdate) => void): this;
on(event: string, listener: (...args: any[]) => void): this {
return super.on(event, listener);
}
emit(event: 'stream-update', update: StreamUpdate): boolean;
emit(event: string, ...args: any[]): boolean {
return super.emit(event, ...args);
}
private static instance: StreamNotifier;
private constructor() {
super();
// Increase max listeners as we might have many sessions
this.setMaxListeners(1000);
}
/**
* Get singleton instance
*/
static getInstance(): StreamNotifier {
if (!StreamNotifier.instance) {
StreamNotifier.instance = new StreamNotifier();
}
return StreamNotifier.instance;
}
/**
* Notify about new stream data
*/
notifyStreamUpdate(sessionId: string, data: string): void {
const update: StreamUpdate = {
sessionId,
data,
timestamp: Date.now(),
};
this.emit('stream-update', update);
}
/**
* Check if there are any listeners for a session
*/
hasListeners(_sessionId: string): boolean {
// Check if there are any listeners at all
return this.listenerCount('stream-update') > 0;
}
}
// Export singleton instance
export const streamNotifier = StreamNotifier.getInstance();

View file

@ -1,5 +1,4 @@
import * as fs from 'fs';
import { streamNotifier } from './stream-notifier.js';
import { Response } from 'express';
interface StreamClient {
@ -14,7 +13,6 @@ interface WatcherInfo {
lastSize: number;
lastMtime: number;
lineBuffer: string;
notificationListener?: (update: { sessionId: string; data: string; timestamp: number }) => void;
}
export class StreamWatcher {
@ -100,10 +98,6 @@ export class StreamWatcher {
if (watcherInfo.watcher) {
watcherInfo.watcher.close();
}
// Remove notification listener
if (watcherInfo.notificationListener) {
streamNotifier.removeListener('stream-update', watcherInfo.notificationListener);
}
this.activeWatchers.delete(sessionId);
}
}
@ -187,27 +181,9 @@ export class StreamWatcher {
* Start watching a file for changes
*/
private startWatching(sessionId: string, streamPath: string, watcherInfo: WatcherInfo): void {
// Check if we should use direct notifications or file watching
const hasListeners = streamNotifier.hasListeners(sessionId);
console.log(`[STREAM] Using file watcher for session ${sessionId}`);
if (hasListeners) {
console.log(`[STREAM] Using direct notifications for session ${sessionId}`);
// Set up direct notification listener for lowest latency
watcherInfo.notificationListener = (update) => {
if (update.sessionId === sessionId) {
// Process the notification data directly
const lines = update.data.split('\n').filter((line) => line.trim());
for (const line of lines) {
this.broadcastLine(sessionId, line, watcherInfo);
}
}
};
streamNotifier.on('stream-update', watcherInfo.notificationListener);
} else {
console.log(`[STREAM] Using file watcher for session ${sessionId} (cross-process)`);
// Use standard fs.watch with stat checking for cross-process scenarios
// Use standard fs.watch with stat checking
watcherInfo.watcher = fs.watch(streamPath, { persistent: true }, (eventType) => {
if (eventType === 'change') {
try {
@ -254,7 +230,6 @@ export class StreamWatcher {
console.error(`[STREAM] File watcher error for session ${sessionId}:`, error);
});
}
}
/**
* Broadcast a line to all clients
@ -328,9 +303,6 @@ export class StreamWatcher {
if (watcherInfo.watcher) {
watcherInfo.watcher.close();
}
if (watcherInfo.notificationListener) {
streamNotifier.removeListener('stream-update', watcherInfo.notificationListener);
}
}
this.activeWatchers.clear();
}