feat: stop single session and auto start all sessions

This commit is contained in:
Anton Voylenko 2025-07-11 20:14:24 +03:00
commit 19797f4268
8 changed files with 162 additions and 7 deletions

View file

@ -1,7 +1,8 @@
const app = require('./src/app') const app = require('./src/app')
const { baseWebhookURL, enableWebHook, enableWebSocket } = require('./src/config') const { baseWebhookURL, enableWebHook, enableWebSocket, autoStartSessions } = require('./src/config')
const { logger } = require('./src/logger') const { logger } = require('./src/logger')
const { handleUpgrade } = require('./src/websocket') const { handleUpgrade } = require('./src/websocket')
const { restoreSessions } = require('./src/sessions')
require('dotenv').config() require('dotenv').config()
@ -17,6 +18,10 @@ if (!baseWebhookURL && enableWebHook) {
const server = app.listen(port, () => { const server = app.listen(port, () => {
logger.info(`Server running on port ${port}`) logger.info(`Server running on port ${port}`)
logger.debug({ configuration: require('./src/config') }, 'Service configuration') logger.debug({ configuration: require('./src/config') }, 'Service configuration')
if (autoStartSessions) {
logger.info('Starting all sessions')
restoreSessions()
}
}) })
if (enableWebSocket) { if (enableWebSocket) {

View file

@ -1,6 +1,5 @@
require('./routes') require('./routes')
const express = require('express') const express = require('express')
const { restoreSessions } = require('./sessions')
const { routes } = require('./routes') const { routes } = require('./routes')
const { maxAttachmentSize } = require('./config') const { maxAttachmentSize } = require('./config')
@ -12,6 +11,4 @@ app.use(express.json({ limit: maxAttachmentSize + 1000000 }))
app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true })) app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true }))
app.use('/', routes) app.use('/', routes)
restoreSessions()
module.exports = app module.exports = app

View file

@ -21,6 +21,7 @@ const releaseBrowserLock = process.env.RELEASE_BROWSER_LOCK ? (process.env.RELEA
const logLevel = process.env.LOG_LEVEL || 'info' const logLevel = process.env.LOG_LEVEL || 'info'
const enableWebHook = process.env.ENABLE_WEBHOOK ? (process.env.ENABLE_WEBHOOK).toLowerCase() === 'true' : true const enableWebHook = process.env.ENABLE_WEBHOOK ? (process.env.ENABLE_WEBHOOK).toLowerCase() === 'true' : true
const enableWebSocket = process.env.ENABLE_WEBSOCKET ? (process.env.ENABLE_WEBSOCKET).toLowerCase() === 'true' : false const enableWebSocket = process.env.ENABLE_WEBSOCKET ? (process.env.ENABLE_WEBSOCKET).toLowerCase() === 'true' : false
const autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true
module.exports = { module.exports = {
sessionFolderPath, sessionFolderPath,
@ -41,5 +42,6 @@ module.exports = {
releaseBrowserLock, releaseBrowserLock,
logLevel, logLevel,
enableWebHook, enableWebHook,
enableWebSocket enableWebSocket,
autoStartSessions
} }

View file

@ -1,5 +1,5 @@
const qr = require('qr-image') const qr = require('qr-image')
const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions') const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, destroySession, sessions } = require('../sessions')
const { sendErrorResponse, waitForNestedObject } = require('../utils') const { sendErrorResponse, waitForNestedObject } = require('../utils')
const { logger } = require('../logger') const { logger } = require('../logger')
@ -51,6 +51,39 @@ const startSession = async (req, res) => {
} }
} }
/**
* Stops a session for the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to stop.
* @returns {Promise<void>}
* @throws {Error} If there was an error stopping the session.
*/
const stopSession = async (req, res) => {
// #swagger.summary = 'Stop session'
// #swagger.description = 'Stops a session for the given session ID.'
const sessionId = req.params.sessionId
try {
await destroySession(sessionId)
/* #swagger.responses[200] = {
description: "Status of the stopped session.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/StopSessionResponse" }
}
}
}
*/
res.json({ success: true, message: 'Session stopped successfully' })
} catch (error) {
logger.error({ sessionId, err: error }, 'Failed to stop session')
sendErrorResponse(res, 500, error.message)
}
}
/** /**
* Status of the session with the given session ID. * Status of the session with the given session ID.
* *
@ -417,6 +450,7 @@ const getPageScreenshot = async (req, res) => {
module.exports = { module.exports = {
startSession, startSession,
stopSession,
statusSession, statusSession,
sessionQrCode, sessionQrCode,
sessionQrCodeImage, sessionQrCodeImage,

View file

@ -38,6 +38,7 @@ routes.use('/session', sessionRouter)
sessionRouter.get('/getSessions', sessionController.getSessions) sessionRouter.get('/getSessions', sessionController.getSessions)
sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession) sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession)
sessionRouter.get('/stop/:sessionId', middleware.sessionNameValidation, sessionController.stopSession)
sessionRouter.get('/status/:sessionId', middleware.sessionNameValidation, sessionController.statusSession) sessionRouter.get('/status/:sessionId', middleware.sessionNameValidation, sessionController.statusSession)
sessionRouter.get('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode) sessionRouter.get('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode)
sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage) sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage)

View file

@ -488,6 +488,33 @@ const reloadSession = async (sessionId) => {
} }
} }
const destroySession = async (sessionId) => {
try {
const client = sessions.get(sessionId)
if (!client) {
return
}
client.pupPage?.removeAllListeners('close')
client.pupPage?.removeAllListeners('error')
try {
await terminateWebSocketServer(sessionId)
} catch (error) {
logger.error({ sessionId, err: error }, 'Failed to terminate WebSocket server')
}
await client.destroy()
// Wait 10 secs for client.pupBrowser to be disconnected
let maxDelay = 0
while (client.pupBrowser?.isConnected() && (maxDelay < 10)) {
await new Promise(resolve => setTimeout(resolve, 1000))
maxDelay++
}
sessions.delete(sessionId)
} catch (error) {
logger.error({ sessionId, err: error }, 'Failed to stop session')
throw error
}
}
const deleteSession = async (sessionId, validation) => { const deleteSession = async (sessionId, validation) => {
try { try {
const client = sessions.get(sessionId) const client = sessions.get(sessionId)
@ -554,5 +581,6 @@ module.exports = {
validateSession, validateSession,
deleteSession, deleteSession,
reloadSession, reloadSession,
flushSessions flushSessions,
destroySession
} }

View file

@ -44,6 +44,10 @@ const doc = {
success: true, success: true,
message: 'Session initiated successfully' message: 'Session initiated successfully'
}, },
StopSessionResponse: {
success: true,
message: 'Session stopped successfully'
},
StatusSessionResponse: { StatusSessionResponse: {
success: true, success: true,
state: 'CONNECTED', state: 'CONNECTED',

View file

@ -220,6 +220,74 @@
] ]
} }
}, },
"/session/stop/{sessionId}": {
"get": {
"tags": [
"Session"
],
"summary": "Stop session",
"description": "Stops a session for the given session ID.",
"parameters": [
{
"name": "sessionId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Unique identifier for the session (alphanumeric and - allowed)",
"example": "f8377d8d-a589-4242-9ba6-9486a04ef80c"
}
],
"responses": {
"200": {
"description": "Status of the stopped session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StopSessionResponse"
}
}
}
},
"403": {
"description": "Forbidden.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ForbiddenResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Server failure.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/session/status/{sessionId}": { "/session/status/{sessionId}": {
"get": { "get": {
"tags": [ "tags": [
@ -11571,6 +11639,22 @@
"name": "StartSessionResponse" "name": "StartSessionResponse"
} }
}, },
"StopSessionResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Session stopped successfully"
}
},
"xml": {
"name": "StopSessionResponse"
}
},
"StatusSessionResponse": { "StatusSessionResponse": {
"type": "object", "type": "object",
"properties": { "properties": {