diff --git a/package-lock.json b/package-lock.json index 59c49315..2e8bbc5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-rate-limit": "^6.9.0", + "qr-image": "^3.2.0", "qrcode-terminal": "^0.12.0", "swagger-ui-express": "^4.6.3", "whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_8" @@ -5975,6 +5976,11 @@ } ] }, + "node_modules/qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha512-rXKDS5Sx3YipVsqmlMJsJsk6jXylEpiHRC2+nJy66fxA5ExYyGa4PqwteW69SaVmAb2OQ18HbYriT7cGQMbduw==" + }, "node_modules/qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", @@ -11811,6 +11817,11 @@ "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", "dev": true }, + "qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha512-rXKDS5Sx3YipVsqmlMJsJsk6jXylEpiHRC2+nJy66fxA5ExYyGa4PqwteW69SaVmAb2OQ18HbYriT7cGQMbduw==" + }, "qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", diff --git a/package.json b/package.json index c67b0c40..3e257cb1 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-rate-limit": "^6.9.0", + "qr-image": "^3.2.0", "qrcode-terminal": "^0.12.0", "swagger-ui-express": "^4.6.3", "whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_8" diff --git a/src/controllers/sessionController.js b/src/controllers/sessionController.js index 09491da8..6e6e64dc 100644 --- a/src/controllers/sessionController.js +++ b/src/controllers/sessionController.js @@ -1,5 +1,6 @@ -const { setupSession, deleteSession, validateSession, flushSessions } = require('../sessions') +const qr = require('qr-image') +const { setupSession, deleteSession, validateSession, flushSessions, sessions } = require('../sessions') const { sendErrorResponse, waitForNestedObject } = require('../utils') /** @@ -102,6 +103,95 @@ const statusSession = async (req, res) => { } } +/** + * QR code of the session with 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 start. + * @returns {Promise} + * @throws {Error} If there was an error getting status of the session. + */ +const sessionQrCode = async (req, res) => { + // #swagger.summary = 'Get session QR code' + // #swagger.description = 'QR code of the session with the given session ID.' + try { + const sessionId = req.params.sessionId + const session = sessions.get(sessionId) + if (!session) { + return res.json({ success: false, message: 'session_not_found' }) + } + if (session.qr) { + return res.json({ success: true, qr: session.qr }) + } + return res.json({ success: false, message: 'qr code not ready or already scanned' }) + } catch (error) { + console.log('sessionQrCode ERROR', error) + /* #swagger.responses[500] = { + description: "Server Failure.", + content: { + "application/json": { + schema: { "$ref": "#/definitions/ErrorResponse" } + } + } + } + */ + sendErrorResponse(res, 500, error.message) + } +} + +/** + * QR code as image of the session with 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 start. + * @returns {Promise} + * @throws {Error} If there was an error getting status of the session. + */ +const sessionQrCodeImage = async (req, res) => { + // #swagger.summary = 'Get session QR code as image' + // #swagger.description = 'QR code as image of the session with the given session ID.' + try { + const sessionId = req.params.sessionId + const session = sessions.get(sessionId) + if (!session) { + return res.json({ success: false, message: 'session_not_found' }) + } + if (session.qr) { + const qrImage = qr.image(session.qr) + /* #swagger.responses[200] = { + description: "QR image.", + content: { + "image/png": {} + } + } + */ + res.writeHead(200, { + 'Content-Type': 'image/png' + }) + return qrImage.pipe(res) + } + return res.json({ success: false, message: 'qr code not ready or already scanned' }) + } catch (error) { + console.log('sessionQrCodeImage ERROR', error) + /* #swagger.responses[500] = { + description: "Server Failure.", + content: { + "application/json": { + schema: { "$ref": "#/definitions/ErrorResponse" } + } + } + } + */ + sendErrorResponse(res, 500, error.message) + } +} + /** * Terminates the session with the given session ID. * @@ -228,6 +318,8 @@ const terminateAllSessions = async (req, res) => { module.exports = { startSession, statusSession, + sessionQrCode, + sessionQrCodeImage, terminateSession, terminateInactiveSessions, terminateAllSessions diff --git a/src/routes.js b/src/routes.js index 8881122e..47cdde80 100644 --- a/src/routes.js +++ b/src/routes.js @@ -38,6 +38,8 @@ routes.use('/session', sessionRouter) sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession) 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) sessionRouter.get('/terminate/:sessionId', middleware.sessionNameValidation, sessionController.terminateSession) sessionRouter.get('/terminateInactive', sessionController.terminateInactiveSessions) sessionRouter.get('/terminateAll', sessionController.terminateAllSessions) diff --git a/src/sessions.js b/src/sessions.js index 7a9242d8..f93ddbd8 100644 --- a/src/sessions.js +++ b/src/sessions.js @@ -259,12 +259,14 @@ const initializeEvents = (client, sessionId) => { }) }) - checkIfEventisEnabled('qr') - .then(_ => { - client.on('qr', (qr) => { + client.on('qr', (qr) => { + // inject qr code into session + client.qr = qr + checkIfEventisEnabled('qr') + .then(_ => { triggerWebhook(sessionWebhook, sessionId, 'qr', { qr }) }) - }) + }) checkIfEventisEnabled('ready') .then(_ => { diff --git a/swagger.json b/swagger.json index 25914e19..e4d8198b 100644 --- a/swagger.json +++ b/swagger.json @@ -208,6 +208,131 @@ ] } }, + "/session/qr/{sessionId}": { + "get": { + "tags": [ + "Session" + ], + "summary": "Get session QR code", + "description": "QR code of the session with 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": "OK" + }, + "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/qr/{sessionId}/image": { + "get": { + "tags": [ + "Session" + ], + "summary": "Get session QR code as image", + "description": "QR code as image of the session with 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": "QR image.", + "content": { + "image/png": {} + } + }, + "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/terminate/{sessionId}": { "get": { "tags": [