diff --git a/.env.example b/.env.example index 8916869..8710b2c 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,7 @@ WEB_VERSION_CACHE_TYPE=none # OPTIONAL, DETERMINTES WHERE TO GET THE WHATSAPP WE RECOVER_SESSIONS=TRUE # OPTIONAL, SHOULD WE RECOVER THE SESSION IN CASE OF PAGE FAILURES CHROME_BIN= # OPTIONAL, PATH TO CHROME BINARY HEADLESS=TRUE # OPTIONAL, RUN CHROME IN HEADLESS MODE +RELEASE_BROWSER_LOCK=TRUE # OPTIONAL, RELEASE THE BROWSER LOCK ON SESSION INITIALIZATION ## Session File Storage ## SESSIONS_PATH=./sessions # OPTIONAL diff --git a/src/config.js b/src/config.js index 5032fc0..1e03954 100644 --- a/src/config.js +++ b/src/config.js @@ -17,6 +17,7 @@ const rateLimitWindowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 1000 const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === 'true' const chromeBin = process.env.CHROME_BIN || null const headless = process.env.HEADLESS ? (process.env.HEADLESS).toLowerCase() === 'true' : true +const releaseBrowserLock = process.env.RELEASE_BROWSER_LOCK ? (process.env.RELEASE_BROWSER_LOCK).toLowerCase() === 'true' : true module.exports = { sessionFolderPath, @@ -33,5 +34,6 @@ module.exports = { rateLimitWindowMs, recoverSessions, chromeBin, - headless + headless, + releaseBrowserLock } diff --git a/src/controllers/healthController.js b/src/controllers/healthController.js index 6aa2f28..300b97f 100644 --- a/src/controllers/healthController.js +++ b/src/controllers/healthController.js @@ -43,11 +43,11 @@ const localCallbackExample = async (req, res) => { try { const { dataType, data } = req.body if (dataType === 'qr') { qrcode.generate(data.qr, { small: true }) } - await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `${JSON.stringify(req.body)}\r\n`, { flag: 'a+' }, _ => _) + await fsp.mkdir(sessionFolderPath, { recursive: true }) + await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `${JSON.stringify(req.body)}\r\n`, { flag: 'a+' }) res.json({ success: true }) } catch (error) { console.log(error) - await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `(ERROR) ${JSON.stringify(error)}\r\n`, { flag: 'a+' }, _ => _) sendErrorResponse(res, 500, error.message) } } diff --git a/src/controllers/sessionController.js b/src/controllers/sessionController.js index d8bd886..8a5ef66 100644 --- a/src/controllers/sessionController.js +++ b/src/controllers/sessionController.js @@ -18,7 +18,7 @@ const startSession = async (req, res) => { // #swagger.description = 'Starts a session for the given session ID.' try { const sessionId = req.params.sessionId - const setupSessionReturn = setupSession(sessionId) + const setupSessionReturn = await setupSession(sessionId) if (!setupSessionReturn.success) { /* #swagger.responses[422] = { description: "Unprocessable Entity.", diff --git a/src/sessions.js b/src/sessions.js index 2278b4d..1b9d4a7 100644 --- a/src/sessions.js +++ b/src/sessions.js @@ -2,7 +2,7 @@ const { Client, LocalAuth } = require('whatsapp-web.js') const fs = require('fs') const path = require('path') const sessions = new Map() -const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless } = require('./config') +const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless, releaseBrowserLock } = require('./config') const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled } = require('./utils') // Function to validate if the session is ready @@ -65,7 +65,7 @@ const restoreSessions = () => { fs.mkdirSync(sessionFolderPath) // Create the session directory if it doesn't exist } // Read the contents of the folder - fs.readdir(sessionFolderPath, (_, files) => { + fs.readdir(sessionFolderPath, async (_, files) => { // Iterate through the files in the parent folder for (const file of files) { // Use regular expression to extract the string from the folder name @@ -73,7 +73,7 @@ const restoreSessions = () => { if (match) { const sessionId = match[1] console.log('existing session detected', sessionId) - setupSession(sessionId) + await setupSession(sessionId) } } }) @@ -84,7 +84,7 @@ const restoreSessions = () => { } // Setup Session -const setupSession = (sessionId) => { +const setupSession = async (sessionId) => { try { if (sessions.has(sessionId)) { return { success: false, message: `Session already exists for: ${sessionId}`, client: sessions.get(sessionId) } @@ -127,7 +127,22 @@ const setupSession = (sessionId) => { const client = new Client(clientOptions) - client.initialize().catch(err => console.log('Initialize error:', err.message)) + if (releaseBrowserLock) { + // See https://github.com/puppeteer/puppeteer/issues/4860 + const singletonLockPath = path.join(`${sessionFolderPath}-${sessionId}`, 'SingletonLock') + const singletonLockExists = await fs.promises.access(singletonLockPath).then(() => true).catch(() => false) + if (singletonLockExists) { + console.log('Browser lock file exists, removing ', sessionId) + await fs.promises.unlink(singletonLockPath) + } + } + + try { + await client.initialize() + } catch (error) { + console.log('Initialize error:', error.message) + throw error + } initializeEvents(client, sessionId) @@ -148,7 +163,7 @@ const initializeEvents = (client, sessionId) => { const restartSession = async (sessionId) => { sessions.delete(sessionId) await client.destroy().catch(e => { }) - setupSession(sessionId) + await setupSession(sessionId) } client.pupPage.once('close', function () { // emitted when the page closes @@ -402,7 +417,7 @@ const reloadSession = async (sessionId) => { } } sessions.delete(sessionId) - setupSession(sessionId) + await setupSession(sessionId) } catch (error) { console.log(error) throw error