Initial commit
This commit is contained in:
454
remote/src/core/App.js
Normal file
454
remote/src/core/App.js
Normal file
@@ -0,0 +1,454 @@
|
||||
const Container = require('./Container');
|
||||
const EventBus = require('./EventBus');
|
||||
const EventTypes = require('./events');
|
||||
const { ErrorHandler } = require('./ErrorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
const MessageTypes = require('../server/messageTypes');
|
||||
|
||||
const SHUTDOWN_TIMEOUT = 30000;
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
this.container = null;
|
||||
this.eventBus = null;
|
||||
this.started = false;
|
||||
this.shuttingDown = false;
|
||||
this.lastClipboardText = '';
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
this.container = new Container();
|
||||
this.eventBus = new EventBus();
|
||||
|
||||
this.container.register('eventBus', () => this.eventBus);
|
||||
|
||||
this._registerConfig();
|
||||
this._registerLogger();
|
||||
this._registerErrorHandler();
|
||||
this._registerAuthServices();
|
||||
this._registerStreamServices();
|
||||
this._registerInputServices();
|
||||
this._registerClipboardServices();
|
||||
this._registerFileServices();
|
||||
this._registerNetworkServices();
|
||||
this._registerServer();
|
||||
|
||||
logger.info('Application bootstrap completed');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_registerConfig() {
|
||||
this.container.register('config', (c) => {
|
||||
return require('../config');
|
||||
});
|
||||
}
|
||||
|
||||
_registerLogger() {
|
||||
this.container.register('logger', (c) => {
|
||||
return require('../utils/logger');
|
||||
});
|
||||
}
|
||||
|
||||
_registerEventBus() {
|
||||
this.container.register('eventBus', (c) => {
|
||||
return this.eventBus;
|
||||
});
|
||||
}
|
||||
|
||||
_registerErrorHandler() {
|
||||
this.container.register('errorHandler', (c) => {
|
||||
return new ErrorHandler();
|
||||
});
|
||||
}
|
||||
|
||||
_registerAuthServices() {
|
||||
this.container.register('authService', (c) => {
|
||||
const AuthService = require('../services/auth/AuthService');
|
||||
return new AuthService();
|
||||
});
|
||||
|
||||
this.container.register('tokenManager', (c) => {
|
||||
const TokenManager = require('../services/auth/TokenManager');
|
||||
return new TokenManager();
|
||||
});
|
||||
}
|
||||
|
||||
_registerStreamServices() {
|
||||
this.container.register('ffmpegEncoder', (c) => {
|
||||
const FFmpegEncoder = require('../services/stream/FFmpegEncoder');
|
||||
return new FFmpegEncoder();
|
||||
});
|
||||
}
|
||||
|
||||
_registerInputServices() {
|
||||
this.container.register('inputService', (c) => {
|
||||
const PowerShellInput = require('../services/input/PowerShellInput');
|
||||
return new PowerShellInput();
|
||||
});
|
||||
|
||||
this.container.register('inputHandler', (c) => {
|
||||
const InputHandler = require('../server/InputHandler');
|
||||
const inputService = c.resolve('inputService');
|
||||
return new InputHandler(inputService);
|
||||
});
|
||||
}
|
||||
|
||||
_registerClipboardServices() {
|
||||
this.container.register('clipboardService', (c) => {
|
||||
const { clipboardService } = require('../services/clipboard');
|
||||
return clipboardService;
|
||||
});
|
||||
}
|
||||
|
||||
_registerFileServices() {
|
||||
this.container.register('fileService', (c) => {
|
||||
const { fileService } = require('../services/file');
|
||||
return fileService;
|
||||
});
|
||||
}
|
||||
|
||||
_registerNetworkServices() {
|
||||
this.container.register('frpService', (c) => {
|
||||
const FRPService = require('../services/network/FRPService');
|
||||
const config = c.resolve('config');
|
||||
const frpConfig = config.getSection('frp') || {};
|
||||
return new FRPService({
|
||||
enabled: frpConfig.enabled !== false
|
||||
});
|
||||
});
|
||||
|
||||
this.container.register('giteaService', (c) => {
|
||||
const GiteaService = require('../services/network/GiteaService');
|
||||
const config = c.resolve('config');
|
||||
const giteaConfig = config.getSection('gitea') || {};
|
||||
return new GiteaService({
|
||||
enabled: giteaConfig.enabled !== false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_registerServer() {
|
||||
this.container.register('httpServer', (c) => {
|
||||
const Server = require('../server/Server');
|
||||
const config = c.resolve('config');
|
||||
const serverConfig = config.getSection('server') || {};
|
||||
return new Server({
|
||||
port: serverConfig.port || 3000,
|
||||
host: serverConfig.host || '0.0.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
this.container.register('wsServer', (c) => {
|
||||
const WebSocketServer = require('../server/WebSocketServer');
|
||||
return new WebSocketServer();
|
||||
});
|
||||
|
||||
this.container.register('streamBroadcaster', (c) => {
|
||||
const StreamBroadcaster = require('../server/StreamBroadcaster');
|
||||
const wsServer = c.resolve('wsServer');
|
||||
return new StreamBroadcaster(wsServer);
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.started) {
|
||||
logger.warn('Application already started');
|
||||
return;
|
||||
}
|
||||
|
||||
const errorHandler = this.container.resolve('errorHandler');
|
||||
errorHandler.initialize();
|
||||
|
||||
this._setupRoutes();
|
||||
|
||||
const httpServer = this.container.resolve('httpServer');
|
||||
const address = await httpServer.start();
|
||||
logger.info('HTTP server started', { address });
|
||||
|
||||
const wsServer = this.container.resolve('wsServer');
|
||||
wsServer.start(httpServer.getHTTPServer());
|
||||
logger.info('WebSocket server started');
|
||||
|
||||
this._setupWebSocketHandlers();
|
||||
|
||||
const ffmpegEncoder = this.container.resolve('ffmpegEncoder');
|
||||
ffmpegEncoder.start();
|
||||
logger.info('FFmpeg encoder started');
|
||||
|
||||
const screenRes = ffmpegEncoder.getScreenResolution();
|
||||
wsServer.setScreenResolution(screenRes.width, screenRes.height);
|
||||
|
||||
const streamBroadcaster = this.container.resolve('streamBroadcaster');
|
||||
streamBroadcaster.setEncoder(ffmpegEncoder);
|
||||
logger.info('Stream broadcaster attached to encoder');
|
||||
|
||||
const inputService = this.container.resolve('inputService');
|
||||
await inputService.start();
|
||||
logger.info('Input service started');
|
||||
|
||||
const frpService = this.container.resolve('frpService');
|
||||
frpService.start();
|
||||
logger.info('FRP service started');
|
||||
|
||||
const giteaService = this.container.resolve('giteaService');
|
||||
giteaService.start();
|
||||
logger.info('Gitea service started');
|
||||
|
||||
// 启动剪贴板监控,主动同步到主控
|
||||
this._startClipboardWatcher();
|
||||
|
||||
this.started = true;
|
||||
|
||||
await this.eventBus.emit(EventTypes.APP_START, {
|
||||
timestamp: new Date().toISOString(),
|
||||
address
|
||||
});
|
||||
|
||||
logger.info('Application started successfully');
|
||||
|
||||
this._setupGracefulShutdown();
|
||||
}
|
||||
|
||||
_setupRoutes() {
|
||||
const httpServer = this.container.resolve('httpServer');
|
||||
const config = this.container.resolve('config');
|
||||
const authService = this.container.resolve('authService');
|
||||
const tokenManager = this.container.resolve('tokenManager');
|
||||
const inputHandler = this.container.resolve('inputHandler');
|
||||
const paths = require('../utils/paths');
|
||||
|
||||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const authMiddleware = require('../middlewares/auth');
|
||||
const routes = require('../routes');
|
||||
|
||||
httpServer.use(cookieParser());
|
||||
httpServer.use(express.json());
|
||||
httpServer.use(express.urlencoded({ extended: true }));
|
||||
|
||||
httpServer.app.post('/login', async (req, res) => {
|
||||
const { password } = req.body;
|
||||
const isValid = await authService.authenticate(password);
|
||||
|
||||
if (isValid) {
|
||||
const token = tokenManager.generateToken({ userId: 'default-user' });
|
||||
res.cookie('auth', token, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 });
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
httpServer.renderLoginPage(res, '密码错误');
|
||||
});
|
||||
|
||||
httpServer.use((req, res, next) => {
|
||||
if (!authService.hasPassword()) {
|
||||
res.locals.authenticated = true;
|
||||
return next();
|
||||
}
|
||||
|
||||
const token = req.cookies && req.cookies.auth;
|
||||
if (token) {
|
||||
const decoded = tokenManager.verifyToken(token);
|
||||
if (decoded) {
|
||||
res.locals.authenticated = true;
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
if (req.path === '/' || req.path === '/index.html') {
|
||||
return httpServer.renderLoginPage(res);
|
||||
}
|
||||
|
||||
res.status(401).json({ error: 'Authentication required' });
|
||||
});
|
||||
|
||||
httpServer.static(paths.getPublicPath());
|
||||
|
||||
httpServer.use('/api', authMiddleware);
|
||||
httpServer.use('/api', routes);
|
||||
|
||||
httpServer.app.get('/api/config', (req, res) => {
|
||||
try {
|
||||
res.json(config.getAll());
|
||||
} catch (error) {
|
||||
logger.error('Error getting config', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to get config' });
|
||||
}
|
||||
});
|
||||
|
||||
const wsServer = this.container.resolve('wsServer');
|
||||
const originalSetup = wsServer.setupConnectionHandler.bind(wsServer);
|
||||
wsServer.setupConnectionHandler = function() {
|
||||
originalSetup();
|
||||
const originalHandlers = this.wss.listeners('connection');
|
||||
this.wss.removeAllListeners('connection');
|
||||
|
||||
const securityConfig = require('../utils/config').getSecurityConfig();
|
||||
const password = securityConfig.password;
|
||||
|
||||
// 未认证的连接也允许,用于剪贴板同步
|
||||
this.wss.on('connection', (ws, req) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const isAuthenticated = url.searchParams.get('password') === password;
|
||||
|
||||
// 保存认证状态
|
||||
ws.isAuthenticated = isAuthenticated;
|
||||
|
||||
// 处理输入消息(不检查认证)
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data);
|
||||
inputHandler.handleMessage(message, ws);
|
||||
} catch (error) {
|
||||
logger.debug('Failed to parse WebSocket message', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 调用原始 handlers(用于已认证的连接)
|
||||
originalHandlers.forEach(handler => {
|
||||
handler(ws, req);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
_setupWebSocketHandlers() {
|
||||
// WebSocket handlers set up in _ aresetupRoutes()
|
||||
// to avoid duplicate message handling
|
||||
}
|
||||
|
||||
_startClipboardWatcher() {
|
||||
const clipboardService = this.container.resolve('clipboardService');
|
||||
const wsServer = this.container.resolve('wsServer');
|
||||
|
||||
clipboardService.read().then(content => {
|
||||
if (content.type === 'text') {
|
||||
this.lastClipboardText = content.data || '';
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const content = await clipboardService.read();
|
||||
if (content.type === 'text' && content.data !== this.lastClipboardText) {
|
||||
this.lastClipboardText = content.data || '';
|
||||
|
||||
const message = JSON.stringify({
|
||||
type: MessageTypes.CLIPBOARD_DATA,
|
||||
contentType: content.type,
|
||||
data: content.data,
|
||||
size: content.size
|
||||
});
|
||||
|
||||
wsServer.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
client.send(message);
|
||||
}
|
||||
});
|
||||
logger.info('Clipboard changed, synced to client');
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_setupGracefulShutdown() {
|
||||
const shutdown = async (signal) => {
|
||||
if (this.shuttingDown) {
|
||||
return;
|
||||
}
|
||||
this.shuttingDown = true;
|
||||
|
||||
logger.info(`Received ${signal}, starting graceful shutdown`);
|
||||
await this.stop();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
|
||||
process.on('exit', () => {
|
||||
logger.info('Process exiting');
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (!this.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Stopping application');
|
||||
|
||||
const shutdownPromise = async () => {
|
||||
const frpService = this.container.resolve('frpService');
|
||||
frpService.stop();
|
||||
logger.info('FRP service stopped');
|
||||
|
||||
const giteaService = this.container.resolve('giteaService');
|
||||
giteaService.stop();
|
||||
logger.info('Gitea service stopped');
|
||||
|
||||
const streamBroadcaster = this.container.resolve('streamBroadcaster');
|
||||
streamBroadcaster.stop();
|
||||
logger.info('Stream broadcaster stopped');
|
||||
|
||||
const ffmpegEncoder = this.container.resolve('ffmpegEncoder');
|
||||
ffmpegEncoder.stop();
|
||||
logger.info('FFmpeg encoder stopped');
|
||||
|
||||
const inputHandler = this.container.resolve('inputHandler');
|
||||
inputHandler.stop();
|
||||
logger.info('Input handler stopped');
|
||||
|
||||
const inputService = this.container.resolve('inputService');
|
||||
await inputService.stop();
|
||||
logger.info('Input service stopped');
|
||||
|
||||
const wsServer = this.container.resolve('wsServer');
|
||||
wsServer.stop();
|
||||
logger.info('WebSocket server stopped');
|
||||
|
||||
const httpServer = this.container.resolve('httpServer');
|
||||
await httpServer.stop();
|
||||
logger.info('HTTP server stopped');
|
||||
|
||||
await this.eventBus.emit(EventTypes.APP_STOP, {
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
this.started = false;
|
||||
logger.info('Application stopped successfully');
|
||||
};
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('Shutdown timeout exceeded'));
|
||||
}, SHUTDOWN_TIMEOUT);
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.race([shutdownPromise(), timeoutPromise]);
|
||||
} catch (error) {
|
||||
logger.error('Error during shutdown', { error: error.message });
|
||||
this.started = false;
|
||||
}
|
||||
}
|
||||
|
||||
getService(name) {
|
||||
if (!this.container) {
|
||||
throw new Error('Application not bootstrapped');
|
||||
}
|
||||
return this.container.resolve(name);
|
||||
}
|
||||
|
||||
getEventBus() {
|
||||
return this.eventBus;
|
||||
}
|
||||
|
||||
isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = App;
|
||||
77
remote/src/core/Container.js
Normal file
77
remote/src/core/Container.js
Normal file
@@ -0,0 +1,77 @@
|
||||
class Container {
|
||||
constructor() {
|
||||
this._services = new Map();
|
||||
this._singletons = new Map();
|
||||
this._resolving = new Set();
|
||||
}
|
||||
|
||||
register(name, factory, isSingleton = true) {
|
||||
if (typeof name !== 'string' || name.trim() === '') {
|
||||
throw new Error('Service name must be a non-empty string');
|
||||
}
|
||||
if (typeof factory !== 'function') {
|
||||
throw new Error('Factory must be a function');
|
||||
}
|
||||
if (this._services.has(name)) {
|
||||
throw new Error(`Service "${name}" is already registered`);
|
||||
}
|
||||
|
||||
this._services.set(name, { factory, isSingleton });
|
||||
return this;
|
||||
}
|
||||
|
||||
resolve(name) {
|
||||
if (!this._services.has(name)) {
|
||||
throw new Error(`Service "${name}" is not registered`);
|
||||
}
|
||||
|
||||
if (this._resolving.has(name)) {
|
||||
const chain = Array.from(this._resolving).join(' -> ');
|
||||
throw new Error(
|
||||
`Circular dependency detected: ${chain} -> ${name}. ` +
|
||||
`This creates a circular reference that cannot be resolved.`
|
||||
);
|
||||
}
|
||||
|
||||
const service = this._services.get(name);
|
||||
|
||||
if (service.isSingleton && this._singletons.has(name)) {
|
||||
return this._singletons.get(name);
|
||||
}
|
||||
|
||||
this._resolving.add(name);
|
||||
|
||||
try {
|
||||
const instance = service.factory(this);
|
||||
|
||||
if (service.isSingleton) {
|
||||
this._singletons.set(name, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
} finally {
|
||||
this._resolving.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
has(name) {
|
||||
return this._services.has(name);
|
||||
}
|
||||
|
||||
unregister(name) {
|
||||
if (!this._services.has(name)) {
|
||||
return false;
|
||||
}
|
||||
this._services.delete(name);
|
||||
this._singletons.delete(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._services.clear();
|
||||
this._singletons.clear();
|
||||
this._resolving.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
81
remote/src/core/ErrorHandler.js
Normal file
81
remote/src/core/ErrorHandler.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
function createErrorResponse(error, code, details) {
|
||||
const response = {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
|
||||
if (code) {
|
||||
response.code = code;
|
||||
}
|
||||
|
||||
if (details !== undefined) {
|
||||
response.details = details;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
class ErrorHandler {
|
||||
constructor() {
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('Uncaught Exception:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
process.exit(1);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection:', {
|
||||
reason: reason instanceof Error
|
||||
? { message: reason.message, stack: reason.stack, name: reason.name }
|
||||
: reason,
|
||||
promise: String(promise)
|
||||
});
|
||||
});
|
||||
|
||||
this.initialized = true;
|
||||
logger.info('Global error handlers initialized');
|
||||
}
|
||||
|
||||
handleError(error, context = {}) {
|
||||
const errorInfo = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
context
|
||||
};
|
||||
|
||||
logger.error('Error occurred:', errorInfo);
|
||||
|
||||
return createErrorResponse(error);
|
||||
}
|
||||
|
||||
createError(message, code, details) {
|
||||
const error = new Error(message);
|
||||
error.code = code;
|
||||
error.details = details;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
const errorHandler = new ErrorHandler();
|
||||
|
||||
module.exports = {
|
||||
ErrorHandler,
|
||||
errorHandler,
|
||||
createErrorResponse
|
||||
};
|
||||
100
remote/src/core/EventBus.js
Normal file
100
remote/src/core/EventBus.js
Normal file
@@ -0,0 +1,100 @@
|
||||
class EventBus {
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on(event, handler) {
|
||||
if (typeof event !== 'string' || event.trim() === '') {
|
||||
throw new Error('Event name must be a non-empty string');
|
||||
}
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error('Handler must be a function');
|
||||
}
|
||||
|
||||
if (!this._listeners.has(event)) {
|
||||
this._listeners.set(event, []);
|
||||
}
|
||||
|
||||
this._listeners.get(event).push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
off(event, handler) {
|
||||
if (!this._listeners.has(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const handlers = this._listeners.get(event);
|
||||
const index = handlers.indexOf(handler);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
handlers.splice(index, 1);
|
||||
|
||||
if (handlers.length === 0) {
|
||||
this._listeners.delete(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async emit(event, data) {
|
||||
if (!this._listeners.has(event)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const handlers = this._listeners.get(event);
|
||||
const results = [];
|
||||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
const result = await handler(data);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
results.push({ error });
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
once(event, handler) {
|
||||
if (typeof event !== 'string' || event.trim() === '') {
|
||||
throw new Error('Event name must be a non-empty string');
|
||||
}
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error('Handler must be a function');
|
||||
}
|
||||
|
||||
const onceHandler = async (data) => {
|
||||
this.off(event, onceHandler);
|
||||
return await handler(data);
|
||||
};
|
||||
|
||||
return this.on(event, onceHandler);
|
||||
}
|
||||
|
||||
removeAllListeners(event) {
|
||||
if (event === undefined) {
|
||||
this._listeners.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof event !== 'string') {
|
||||
throw new Error('Event name must be a string');
|
||||
}
|
||||
|
||||
return this._listeners.delete(event);
|
||||
}
|
||||
|
||||
listenerCount(event) {
|
||||
if (!this._listeners.has(event)) {
|
||||
return 0;
|
||||
}
|
||||
return this._listeners.get(event).length;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventBus;
|
||||
27
remote/src/core/events.js
Normal file
27
remote/src/core/events.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const EventTypes = {
|
||||
STREAM_START: 'stream:start',
|
||||
STREAM_STOP: 'stream:stop',
|
||||
STREAM_DATA: 'stream:data',
|
||||
STREAM_ERROR: 'stream:error',
|
||||
CLIENT_CONNECTED: 'client:connected',
|
||||
CLIENT_DISCONNECTED: 'client:disconnected',
|
||||
INPUT_EVENT: 'input:event',
|
||||
APP_START: 'app:start',
|
||||
APP_STOP: 'app:stop',
|
||||
ERROR: 'error',
|
||||
|
||||
// Agent events
|
||||
AGENT_CONNECTED: 'agent:connected',
|
||||
AGENT_DISCONNECTED: 'agent:disconnected',
|
||||
AGENT_REGISTERED: 'agent:registered',
|
||||
AGENT_HEARTBEAT: 'agent:heartbeat',
|
||||
AGENT_STREAM_START: 'agent:stream:start',
|
||||
AGENT_STREAM_STOP: 'agent:stream:stop',
|
||||
|
||||
// Controller events
|
||||
CONTROLLER_CONNECTED: 'controller:connected',
|
||||
CONTROLLER_DISCONNECTED: 'controller:disconnected',
|
||||
CONTROLLER_STREAM_SWITCH: 'controller:stream:switch'
|
||||
};
|
||||
|
||||
module.exports = EventTypes;
|
||||
Reference in New Issue
Block a user