-
-
Notifications
You must be signed in to change notification settings - Fork 399
Expand file tree
/
Copy pathserver.ts
More file actions
177 lines (150 loc) · 5.87 KB
/
Copy pathserver.ts
File metadata and controls
177 lines (150 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import express from 'express'
import * as http from 'http'
import * as path from 'path'
import { Server } from 'socket.io'
import { promises as fsPromise } from 'fs'
import { Request, Response } from 'express'
import { AuthManager } from './AuthManager'
import { ConnectionManager } from '../backend/src/index'
import ConfigStorage from '../backend/src/ConfigStorage'
import { SocketIOServerEventBus } from '../events/EventSystem/SocketIOServerEventBus'
import { Rpc } from '../events/EventSystem/Rpc'
import { makeOpenDialogRpc, makeSaveDialogRpc } from '../events/OpenDialogRequest'
import { getAppVersion, writeToFile, readFromFile } from '../events'
import { RpcEvents } from '../events/EventsV2'
const PORT = process.env.PORT || 3000
const CREDENTIALS_PATH = path.join(process.cwd(), 'data', 'credentials.json')
async function startServer() {
// Initialize authentication
const authManager = new AuthManager(CREDENTIALS_PATH)
await authManager.initialize()
// Create Express app
const app = express()
const server = http.createServer(app)
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
allowEIO3: true, // Allow Engine.IO v3 clients (backwards compatibility)
transports: ['websocket', 'polling'], // Support both transports
pingTimeout: 60000, // Increase ping timeout
pingInterval: 25000, // Ping interval
})
// Authentication middleware for Socket.io
io.use(async (socket, next) => {
const { username, password } = socket.handshake.auth
if (!username || !password) {
return next(new Error('Authentication required'))
}
const isValid = await authManager.verifyCredentials(username, password)
if (!isValid) {
return next(new Error('Invalid credentials'))
}
console.log('Client authenticated:', username)
next()
})
// Initialize backend event bus with Socket.io
const backendEvents = new SocketIOServerEventBus(io)
const backendRpc = new Rpc(backendEvents)
// Initialize connection manager
const connectionManager = new ConnectionManager(backendEvents)
connectionManager.manageConnections()
// Initialize config storage
const configStorage = new ConfigStorage(path.join(process.cwd(), 'data', 'settings.json'), backendRpc)
configStorage.init()
// Setup RPC handlers for file operations
backendRpc.on(makeOpenDialogRpc(), async request => {
// In browser mode, file selection is handled client-side via upload
// Return empty result as this will be handled differently
return { canceled: true, filePaths: [] }
})
backendRpc.on(makeSaveDialogRpc(), async request => {
// In browser mode, file saving is handled client-side via download
return { canceled: true, filePath: '' }
})
backendRpc.on(getAppVersion, async () => {
// Return version from package.json
try {
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json')
const packageJsonData = await fsPromise.readFile(packageJsonPath, 'utf8')
const packageJson = JSON.parse(packageJsonData)
return packageJson.version
} catch (e) {
return '0.0.0'
}
})
backendRpc.on(writeToFile, async ({ filePath, data, encoding }) => {
// In browser mode, we store files in the server's data directory
const dataDir = path.join(process.cwd(), 'data', 'uploads')
const safePath = path.join(dataDir, path.basename(filePath))
try {
await fsPromise.mkdir(dataDir, { recursive: true })
if (encoding) {
await fsPromise.writeFile(safePath, Buffer.from(data, 'base64'), { encoding: encoding as BufferEncoding })
} else {
await fsPromise.writeFile(safePath, Buffer.from(data, 'base64'))
}
} catch (error) {
console.error('Error writing file:', error)
throw error
}
})
backendRpc.on(readFromFile, async ({ filePath, encoding }) => {
// In browser mode, files are read from the server's data directory
const dataDir = path.join(process.cwd(), 'data', 'uploads')
const safePath = path.join(dataDir, path.basename(filePath))
try {
if (encoding) {
const content = await fsPromise.readFile(safePath, { encoding: encoding as BufferEncoding })
return Buffer.from(content)
}
return await fsPromise.readFile(safePath)
} catch (error) {
console.error('Error reading file:', error)
throw error
}
})
// Certificate upload handler - via IPC for consistency
backendRpc.on(RpcEvents.uploadCertificate, async ({ filename, data }) => {
// Store certificate on server for browser mode
const dataDir = path.join(process.cwd(), 'data', 'certificates')
await fsPromise.mkdir(dataDir, { recursive: true })
const safePath = path.join(dataDir, path.basename(filename))
await fsPromise.writeFile(safePath, Buffer.from(data, 'base64'))
console.log('Certificate uploaded:', filename)
// Return the certificate data for client to use
return {
name: filename,
data,
}
})
// Serve static files
app.use(express.static(path.join(__dirname, '..', '..', 'app', 'build')))
// Serve index.html for all other routes (SPA)
app.use((req: Request, res: Response) => {
res.sendFile(path.join(__dirname, '..', '..', 'app', 'index.html'))
})
// Start server
server.listen(PORT, () => {
console.log('='.repeat(60))
console.log(`MQTT Explorer server running on http://localhost:${PORT}`)
console.log('='.repeat(60))
})
// Handle graceful shutdown
process.on('SIGTERM' as any, () => {
console.log('SIGTERM received, closing connections...')
connectionManager.closeAllConnections()
server.close()
})
process.on('SIGINT' as any, () => {
console.log('SIGINT received, closing connections...')
connectionManager.closeAllConnections()
server.close()
process.exit(0)
})
}
startServer().catch(error => {
console.error('Failed to start server:', error)
process.exit(1)
})