321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
import fs from 'node:fs'
|
|
import { Server as SocketIOServer } from 'socket.io'
|
|
import http from 'node:http'
|
|
import LineConnection from '../app/services/line_connection.js'
|
|
import { ApplicationService } from '@adonisjs/core/types'
|
|
import env from '#start/env'
|
|
import { CustomServer, CustomSocket } from '../app/ultils/types.js'
|
|
import Line from '#models/line'
|
|
import Station from '#models/station'
|
|
|
|
export default class SocketIoProvider {
|
|
private static _io: CustomServer
|
|
constructor(protected app: ApplicationService) {}
|
|
|
|
/**
|
|
* Register bindings to the container
|
|
*/
|
|
register() {}
|
|
|
|
/**
|
|
* The container bindings have booted
|
|
*/
|
|
async boot() {}
|
|
|
|
/**
|
|
* The application has been booted
|
|
*/
|
|
async start() {}
|
|
|
|
/**
|
|
* The process has been started
|
|
*/
|
|
async ready() {
|
|
if (process.argv[1].includes('server.js')) {
|
|
const webSocket = new WebSocketIo(this.app)
|
|
SocketIoProvider._io = await webSocket.boot()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Preparing to shutdown the app
|
|
*/
|
|
async shutdown() {}
|
|
|
|
public static get io() {
|
|
return this._io
|
|
}
|
|
}
|
|
|
|
export class WebSocketIo {
|
|
intervalMap: { [key: string]: NodeJS.Timeout } = {}
|
|
stationMap: Map<number, Station> = new Map()
|
|
lineMap: Map<number, LineConnection> = new Map() // key = lineId
|
|
lineConnecting: number[] = [] // key = lineId
|
|
userConnecting: Map<number, { userId: number; userName: string }> = new Map()
|
|
|
|
constructor(protected app: ApplicationService) {}
|
|
|
|
async boot() {
|
|
const SOCKET_IO_PORT = env.get('SOCKET_PORT') || 8989
|
|
const FRONTEND_URL = env.get('FRONTEND_URL') || 'http://localhost:5173'
|
|
|
|
const socketServer = http.createServer()
|
|
const io = new SocketIOServer(socketServer, {
|
|
pingInterval: 25000, // 25s server gửi ping
|
|
pingTimeout: 20000, // chờ 20s không có pong thì disconnect
|
|
cors: {
|
|
origin: [FRONTEND_URL],
|
|
methods: ['GET', 'POST'],
|
|
credentials: true,
|
|
},
|
|
})
|
|
|
|
io.on('connection', (socket: CustomSocket) => {
|
|
const { userId, userName } = socket.handshake.auth
|
|
console.log('Socket connected:', socket.id)
|
|
socket.connectionTime = new Date()
|
|
this.userConnecting.set(userId, { userId, userName })
|
|
|
|
setTimeout(() => {
|
|
const listUser = Array.from(this.userConnecting.values())
|
|
if (!listUser.find((el) => el.userId === userId)) {
|
|
listUser.push({ userId, userName })
|
|
}
|
|
io.emit('user_connecting', listUser)
|
|
}, 500)
|
|
|
|
setTimeout(() => {
|
|
io.to(socket.id).emit(
|
|
'init',
|
|
Array.from(this.lineMap.values()).map((el) => el.config)
|
|
)
|
|
}, 500)
|
|
|
|
socket.on('disconnect', () => {
|
|
console.log(`FE disconnected: ${socket.id}`)
|
|
this.userConnecting.delete(userId)
|
|
setTimeout(() => {
|
|
io.emit('user_connecting', Array.from(this.userConnecting.values()))
|
|
}, 200)
|
|
})
|
|
|
|
// FE gửi yêu cầu connect lines
|
|
socket.on('connect_lines', async (data) => {
|
|
const { stationData, linesData } = data
|
|
await this.connectLine(io, linesData, stationData)
|
|
})
|
|
|
|
socket.on('write_command_line_from_web', async (data) => {
|
|
const { lineIds, stationId, command } = data
|
|
for (const lineId of lineIds) {
|
|
const line = this.lineMap.get(lineId)
|
|
if (line && line.config.status === 'connected') {
|
|
this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId)
|
|
this.setTimeoutConnect(lineId, line)
|
|
line.writeCommand(command)
|
|
} else {
|
|
if (this.lineConnecting.includes(lineId)) continue
|
|
const linesData = await Line.findBy('id', lineId)
|
|
const stationData = await Station.findBy('id', stationId)
|
|
if (linesData && stationData) {
|
|
this.lineConnecting.push(lineId)
|
|
await this.connectLine(io, [linesData], stationData)
|
|
const lineReconnect = this.lineMap.get(lineId)
|
|
if (lineReconnect) {
|
|
this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId)
|
|
this.setTimeoutConnect(lineId, lineReconnect)
|
|
lineReconnect.writeCommand(command)
|
|
}
|
|
} else {
|
|
io.emit('line_disconnected', {
|
|
stationId,
|
|
lineId,
|
|
status: 'disconnected',
|
|
})
|
|
io.emit('line_error', { lineId, error: 'Line not connected\r\n' })
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on('run_scenario', async (data) => {
|
|
const lineId = data.id
|
|
const scenario = data.scenario
|
|
const line = this.lineMap.get(lineId)
|
|
if (line && line.config.status === 'connected') {
|
|
this.setTimeoutConnect(
|
|
lineId,
|
|
line,
|
|
scenario?.timeout ? Number(scenario?.timeout) + 180000 : 300000
|
|
)
|
|
line.runScript(scenario)
|
|
} else {
|
|
const linesData = await Line.findBy('id', lineId)
|
|
const stationData = await Station.findBy('id', data.station_id)
|
|
if (linesData && stationData) {
|
|
await this.connectLine(io, [linesData], stationData)
|
|
const lineReconnect = this.lineMap.get(lineId)
|
|
if (lineReconnect) {
|
|
this.setTimeoutConnect(lineId, lineReconnect, 300000)
|
|
lineReconnect.runScript(scenario)
|
|
}
|
|
} else {
|
|
io.emit('line_disconnected', {
|
|
stationId: data.stationId,
|
|
lineId,
|
|
status: 'disconnected',
|
|
})
|
|
io.emit('line_error', { lineId, error: 'Line not connected\r\n' })
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on('open_cli', async (data) => {
|
|
const { lineId, userEmail, userName: name, stationId } = data
|
|
const line = this.lineMap.get(lineId)
|
|
if (line) {
|
|
line.userOpenCLI({ userEmail, userName: name })
|
|
} else {
|
|
if (this.lineConnecting.includes(lineId)) return
|
|
const linesData = await Line.findBy('id', lineId)
|
|
const stationData = await Station.findBy('id', stationId)
|
|
if (linesData && stationData) {
|
|
this.lineConnecting.push(lineId)
|
|
await this.connectLine(io, [linesData], stationData)
|
|
const lineReconnect = this.lineMap.get(lineId)
|
|
if (lineReconnect) {
|
|
lineReconnect.userOpenCLI({ userEmail, userName: name })
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on('close_cli', async (data) => {
|
|
const { lineId, stationId } = data
|
|
const line = this.lineMap.get(lineId)
|
|
if (line) {
|
|
line.userCloseCLI()
|
|
} else {
|
|
if (this.lineConnecting.includes(lineId)) return
|
|
const linesData = await Line.findBy('id', lineId)
|
|
const stationData = await Station.findBy('id', stationId)
|
|
if (linesData && stationData) {
|
|
this.lineConnecting.push(lineId)
|
|
await this.connectLine(io, [linesData], stationData)
|
|
const lineReconnect = this.lineMap.get(lineId)
|
|
if (lineReconnect) {
|
|
lineReconnect.userCloseCLI()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on('request_take_over', async (data) => {
|
|
io.emit('confirm_take_over', data)
|
|
})
|
|
|
|
socket.on('get_list_logs', async () => {
|
|
let getListSystemLogs = fs
|
|
.readdirSync('storage/system_logs')
|
|
.map((f) => 'storage/system_logs/' + f)
|
|
io.to(socket.id).emit('list_logs', getListSystemLogs)
|
|
})
|
|
|
|
socket.on('get_content_log', async (data) => {
|
|
try {
|
|
const { line, socketId } = data
|
|
const filePath = line.systemLogUrl
|
|
if (fs.existsSync(filePath)) {
|
|
// Get file stats
|
|
const stats = fs.statSync(filePath)
|
|
const fileSizeInBytes = stats.size
|
|
if (fileSizeInBytes / 1024 / 1024 > 0.5) {
|
|
// File is larger than 0.5 MB
|
|
const fileId = Date.now() // Mã định danh file
|
|
const chunkSize = 64 * 1024 // 64KB
|
|
const fileBuffer = fs.readFileSync(filePath)
|
|
const totalChunks = Math.ceil(fileBuffer.length / chunkSize)
|
|
|
|
for (let i = 0; i < totalChunks; i++) {
|
|
const chunk = fileBuffer.slice(i * chunkSize, (i + 1) * chunkSize)
|
|
io.to(socketId).emit('response_content_log', {
|
|
type: 'chunk',
|
|
chunk: {
|
|
fileId,
|
|
chunkIndex: i,
|
|
totalChunks,
|
|
chunk,
|
|
},
|
|
})
|
|
}
|
|
} else {
|
|
console.log(filePath)
|
|
const content = fs.readFileSync(filePath)
|
|
socket.emit('response_content_log', content)
|
|
}
|
|
} else {
|
|
io.to(socketId).emit('response_content_log', Buffer.from('File not found', 'utf-8'))
|
|
}
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
})
|
|
})
|
|
|
|
socketServer.listen(SOCKET_IO_PORT, () => {
|
|
console.log(`Socket server is running on port ${SOCKET_IO_PORT}`)
|
|
})
|
|
|
|
return io
|
|
}
|
|
|
|
private async connectLine(socket: any, lines: Line[], station: Station) {
|
|
try {
|
|
this.stationMap.set(station.id, station)
|
|
for (const line of lines) {
|
|
const lineConn = new LineConnection(
|
|
{
|
|
id: line.id,
|
|
port: line.port,
|
|
ip: station.ip,
|
|
lineNumber: line.lineNumber,
|
|
stationId: station.id,
|
|
apcName: line.apcName,
|
|
output: '',
|
|
status: '',
|
|
openCLI: false,
|
|
userEmailOpenCLI: '',
|
|
userOpenCLI: '',
|
|
data: [],
|
|
},
|
|
socket
|
|
)
|
|
await lineConn.connect()
|
|
lineConn.writeCommand('\r\n\r\n')
|
|
this.lineMap.set(line.id, lineConn)
|
|
this.setTimeoutConnect(line.id, lineConn)
|
|
}
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
|
|
private setTimeoutConnect = (lineId: number, lineConn: LineConnection, timeout = 180000) => {
|
|
if (this.intervalMap[`${lineId}`]) {
|
|
clearInterval(this.intervalMap[`${lineId}`])
|
|
delete this.intervalMap[`${lineId}`]
|
|
}
|
|
const interval = setInterval(() => {
|
|
lineConn.disconnect()
|
|
// this.lineMap.delete(lineId)
|
|
if (this.intervalMap[`${lineId}`]) {
|
|
clearInterval(this.intervalMap[`${lineId}`])
|
|
delete this.intervalMap[`${lineId}`]
|
|
}
|
|
}, timeout)
|
|
|
|
this.intervalMap[`${lineId}`] = interval
|
|
}
|
|
}
|