316 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			316 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(() => {
 | 
						|
        io.emit('user_connecting', Array.from(this.userConnecting.values()))
 | 
						|
      }, 200)
 | 
						|
 | 
						|
      setTimeout(() => {
 | 
						|
        io.to(socket.id).emit(
 | 
						|
          'init',
 | 
						|
          Array.from(this.lineMap.values()).map((el) => el.config)
 | 
						|
        )
 | 
						|
      }, 200)
 | 
						|
 | 
						|
      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: '',
 | 
						|
          },
 | 
						|
          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
 | 
						|
  }
 | 
						|
}
 |