From 19797f42684608c104173952c7ac89d362b9546a Mon Sep 17 00:00:00 2001 From: Anton Voylenko Date: Fri, 11 Jul 2025 20:14:24 +0300 Subject: [PATCH] feat: stop single session and auto start all sessions --- server.js | 7 ++- src/app.js | 3 - src/config.js | 4 +- src/controllers/sessionController.js | 36 +++++++++++- src/routes.js | 1 + src/sessions.js | 30 +++++++++- swagger.js | 4 ++ swagger.json | 84 ++++++++++++++++++++++++++++ 8 files changed, 162 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index 03b7906..ec7b088 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,8 @@ 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 { handleUpgrade } = require('./src/websocket') +const { restoreSessions } = require('./src/sessions') require('dotenv').config() @@ -17,6 +18,10 @@ if (!baseWebhookURL && enableWebHook) { const server = app.listen(port, () => { logger.info(`Server running on port ${port}`) logger.debug({ configuration: require('./src/config') }, 'Service configuration') + if (autoStartSessions) { + logger.info('Starting all sessions') + restoreSessions() + } }) if (enableWebSocket) { diff --git a/src/app.js b/src/app.js index aeeeb9c..1a3041b 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,5 @@ require('./routes') const express = require('express') -const { restoreSessions } = require('./sessions') const { routes } = require('./routes') 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('/', routes) -restoreSessions() - module.exports = app diff --git a/src/config.js b/src/config.js index 93a8021..3cd8f26 100644 --- a/src/config.js +++ b/src/config.js @@ -21,6 +21,7 @@ const releaseBrowserLock = process.env.RELEASE_BROWSER_LOCK ? (process.env.RELEA const logLevel = process.env.LOG_LEVEL || 'info' 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 autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true module.exports = { sessionFolderPath, @@ -41,5 +42,6 @@ module.exports = { releaseBrowserLock, logLevel, enableWebHook, - enableWebSocket + enableWebSocket, + autoStartSessions } diff --git a/src/controllers/sessionController.js b/src/controllers/sessionController.js index 76a06ca..7f395c2 100644 --- a/src/controllers/sessionController.js +++ b/src/controllers/sessionController.js @@ -1,5 +1,5 @@ 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 { 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} + * @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. * @@ -417,6 +450,7 @@ const getPageScreenshot = async (req, res) => { module.exports = { startSession, + stopSession, statusSession, sessionQrCode, sessionQrCodeImage, diff --git a/src/routes.js b/src/routes.js index 133edb1..b1ed00b 100644 --- a/src/routes.js +++ b/src/routes.js @@ -38,6 +38,7 @@ routes.use('/session', sessionRouter) sessionRouter.get('/getSessions', sessionController.getSessions) 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('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode) sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage) diff --git a/src/sessions.js b/src/sessions.js index 625458d..b82af88 100644 --- a/src/sessions.js +++ b/src/sessions.js @@ -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) => { try { const client = sessions.get(sessionId) @@ -554,5 +581,6 @@ module.exports = { validateSession, deleteSession, reloadSession, - flushSessions + flushSessions, + destroySession } diff --git a/swagger.js b/swagger.js index 672b45b..c68bc63 100644 --- a/swagger.js +++ b/swagger.js @@ -44,6 +44,10 @@ const doc = { success: true, message: 'Session initiated successfully' }, + StopSessionResponse: { + success: true, + message: 'Session stopped successfully' + }, StatusSessionResponse: { success: true, state: 'CONNECTED', diff --git a/swagger.json b/swagger.json index 9d425e8..8149f76 100644 --- a/swagger.json +++ b/swagger.json @@ -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}": { "get": { "tags": [ @@ -11571,6 +11639,22 @@ "name": "StartSessionResponse" } }, + "StopSessionResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Session stopped successfully" + } + }, + "xml": { + "name": "StopSessionResponse" + } + }, "StatusSessionResponse": { "type": "object", "properties": {