From db693beebf0d46e3c78382d453fe0c81a7119cc9 Mon Sep 17 00:00:00 2001 From: Ranjan Kumar Singh Date: Mon, 26 Aug 2024 23:39:22 +0530 Subject: [PATCH 1/3] Fixes disconnect issue when app goes to sleep or becomes active --- README.md | 1 + index.d.ts | 1 + src/EventSource.js | 94 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3b4fc44..004d1c1 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ const options: EventSourceOptions = { debug: false, // Show console.debug messages for debugging purpose. Default: false pollingInterval: 5000, // Time (ms) between reconnections. If set to 0, reconnections will be disabled. Default: 5000 lineEndingCharacter: null // Character(s) used to represent line endings in received data. Common values: '\n' for LF (Unix/Linux), '\r\n' for CRLF (Windows), '\r' for CR (older Mac). Default: null (Automatically detect from event) + reconnectOnActive: true, // Automatically reconnect when the app becomes active (e.g., after being in the background) Default } ``` diff --git a/index.d.ts b/index.d.ts index eb77c42..d144022 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,6 +50,7 @@ export interface EventSourceOptions { debug?: boolean; pollingInterval?: number; lineEndingCharacter?: string; + reconnectOnActive?: boolean; } type BuiltInEventMap = { diff --git a/src/EventSource.js b/src/EventSource.js index 6a7b146..a24f2a2 100644 --- a/src/EventSource.js +++ b/src/EventSource.js @@ -1,3 +1,5 @@ +import { AppState } from 'react-native'; + const XMLReadyStateMap = [ 'UNSENT', 'OPENED', @@ -36,12 +38,21 @@ class EventSource { this.debug = options.debug || false; this.interval = options.pollingInterval ?? 5000; this.lineEndingCharacter = options.lineEndingCharacter || null; + this.reconnectOnActive = options.reconnectOnActive || false; this._xhr = null; this._pollTimer = null; this._lastIndexProcessed = 0; - if (!url || (typeof url !== 'string' && typeof url.toString !== 'function')) { + this._isActive = true; + + this._appStateSubscription = null; + this._setupAppStateListener(); + + if ( + !url || + (typeof url !== 'string' && typeof url.toString !== 'function') + ) { throw new SyntaxError('[EventSource] Invalid URL argument.'); } @@ -54,6 +65,28 @@ class EventSource { this._pollAgain(this.timeoutBeforeConnection, true); } + _setupAppStateListener() { + this._appStateSubscription = AppState.addEventListener( + 'change', + this._handleAppStateChange + ); + } + + _handleAppStateChange = (nextAppState) => { + if (nextAppState === 'active') { + this._logDebug('[EventSource] App became active, reconnecting...'); + this.close(); + if (this.reconnectOnActive) { + this._pollAgain(0, true); // reconnect if the option is enabled + } + } else if (nextAppState === 'background' || nextAppState === 'inactive') { + this._logDebug( + '[EventSource] App went to background, closing connection...' + ); + this.close(); + } + }; + _pollAgain(time, allowZero) { if (time > 0 || allowZero) { this._logDebug(`[EventSource] Will open new connection in ${time} ms.`); @@ -99,9 +132,17 @@ class EventSource { const xhr = this._xhr; - this._logDebug(`[EventSource][onreadystatechange] ReadyState: ${XMLReadyStateMap[xhr.readyState] || 'Unknown'}(${xhr.readyState}), status: ${xhr.status}`); - - if (![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes(xhr.readyState)) { + this._logDebug( + `[EventSource][onreadystatechange] ReadyState: ${ + XMLReadyStateMap[xhr.readyState] || 'Unknown' + }(${xhr.readyState}), status: ${xhr.status}` + ); + + if ( + ![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes( + xhr.readyState + ) + ) { return; } @@ -109,13 +150,17 @@ class EventSource { if (this.status === this.CONNECTING) { this.status = this.OPEN; this.dispatch('open', { type: 'open' }); - this._logDebug('[EventSource][onreadystatechange][OPEN] Connection opened.'); + this._logDebug( + '[EventSource][onreadystatechange][OPEN] Connection opened.' + ); } this._handleEvent(xhr.responseText || ''); if (xhr.readyState === XMLHttpRequest.DONE) { - this._logDebug('[EventSource][onreadystatechange][DONE] Operation done.'); + this._logDebug( + '[EventSource][onreadystatechange][DONE] Operation done.' + ); this._pollAgain(this.interval, false); } } else if (xhr.status !== 0) { @@ -128,7 +173,9 @@ class EventSource { }); if (xhr.readyState === XMLHttpRequest.DONE) { - this._logDebug('[EventSource][onreadystatechange][ERROR] Response status error.'); + this._logDebug( + '[EventSource][onreadystatechange][ERROR] Response status error.' + ); this._pollAgain(this.interval, false); } } @@ -182,10 +229,16 @@ class EventSource { if (this.lineEndingCharacter === null) { const detectedNewlineChar = this._detectNewlineChar(response); if (detectedNewlineChar !== null) { - this._logDebug(`[EventSource] Automatically detected lineEndingCharacter: ${JSON.stringify(detectedNewlineChar).slice(1, -1)}`); + this._logDebug( + `[EventSource] Automatically detected lineEndingCharacter: ${JSON.stringify( + detectedNewlineChar + ).slice(1, -1)}` + ); this.lineEndingCharacter = detectedNewlineChar; } else { - console.warn("[EventSource] Unable to identify the line ending character. Ensure your server delivers a standard line ending character: \\r\\n, \\n, \\r, or specify your custom character using the 'lineEndingCharacter' option."); + console.warn( + '[EventSource] Unable to identify the line ending character. Ensure your server delivers a standard line ending character: \\r\\n, \\n, \\r, or specify your custom character using the "lineEndingCharacter" option.' + ); return; } } @@ -195,7 +248,9 @@ class EventSource { return; } - const parts = response.substring(this._lastIndexProcessed, indexOfDoubleNewline).split(this.lineEndingCharacter); + const parts = response + .substring(this._lastIndexProcessed, indexOfDoubleNewline) + .split(this.lineEndingCharacter); this._lastIndexProcessed = indexOfDoubleNewline; let type = undefined; @@ -252,7 +307,8 @@ class EventSource { } _getLastDoubleNewlineIndex(response) { - const doubleLineEndingCharacter = this.lineEndingCharacter + this.lineEndingCharacter; + const doubleLineEndingCharacter = + this.lineEndingCharacter + this.lineEndingCharacter; const lastIndex = response.lastIndexOf(doubleLineEndingCharacter); if (lastIndex === -1) { return -1; @@ -271,7 +327,9 @@ class EventSource { removeEventListener(type, listener) { if (this.eventHandlers[type] !== undefined) { - this.eventHandlers[type] = this.eventHandlers[type].filter((handler) => handler !== listener); + this.eventHandlers[type] = this.eventHandlers[type].filter( + (handler) => handler !== listener + ); } } @@ -284,7 +342,9 @@ class EventSource { } } else { if (!availableTypes.includes(type)) { - throw Error(`[EventSource] '${type}' type is not supported event type.`); + throw Error( + `[EventSource] '${type}' type is not supported event type.` + ); } this.eventHandlers[type] = []; @@ -306,6 +366,7 @@ class EventSource { close() { if (this.status !== this.CLOSED) { this.status = this.CLOSED; + this._isActive = false; this.dispatch('close', { type: 'close' }); } @@ -313,7 +374,12 @@ class EventSource { if (this._xhr) { this._xhr.abort(); } + + if (this._appStateSubscription) { + this._appStateSubscription.remove(); + this._appStateSubscription = null; + } } } -export default EventSource; +export default EventSource; \ No newline at end of file From ee91a9d25ce1fafdd13e2a599f303b5c3ee7c4ef Mon Sep 17 00:00:00 2001 From: Ranjan Kumar Singh Date: Tue, 27 Aug 2024 02:32:09 +0530 Subject: [PATCH 2/3] reverted formatting issues --- src/EventSource.js | 60 +++++++++++----------------------------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/src/EventSource.js b/src/EventSource.js index a24f2a2..9ab6b76 100644 --- a/src/EventSource.js +++ b/src/EventSource.js @@ -44,15 +44,11 @@ class EventSource { this._pollTimer = null; this._lastIndexProcessed = 0; - this._isActive = true; - this._appStateSubscription = null; this._setupAppStateListener(); - if ( - !url || - (typeof url !== 'string' && typeof url.toString !== 'function') - ) { + + if (!url || (typeof url !== 'string' && typeof url.toString !== 'function')) { throw new SyntaxError('[EventSource] Invalid URL argument.'); } @@ -132,17 +128,9 @@ class EventSource { const xhr = this._xhr; - this._logDebug( - `[EventSource][onreadystatechange] ReadyState: ${ - XMLReadyStateMap[xhr.readyState] || 'Unknown' - }(${xhr.readyState}), status: ${xhr.status}` - ); - - if ( - ![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes( - xhr.readyState - ) - ) { + this._logDebug(`[EventSource][onreadystatechange] ReadyState: ${XMLReadyStateMap[xhr.readyState] || 'Unknown'}(${xhr.readyState}), status: ${xhr.status}`); + + if (![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes(xhr.readyState)) { return; } @@ -150,17 +138,13 @@ class EventSource { if (this.status === this.CONNECTING) { this.status = this.OPEN; this.dispatch('open', { type: 'open' }); - this._logDebug( - '[EventSource][onreadystatechange][OPEN] Connection opened.' - ); + this._logDebug('[EventSource][onreadystatechange][OPEN] Connection opened.'); } this._handleEvent(xhr.responseText || ''); if (xhr.readyState === XMLHttpRequest.DONE) { - this._logDebug( - '[EventSource][onreadystatechange][DONE] Operation done.' - ); + this._logDebug('[EventSource][onreadystatechange][DONE] Operation done.'); this._pollAgain(this.interval, false); } } else if (xhr.status !== 0) { @@ -173,9 +157,7 @@ class EventSource { }); if (xhr.readyState === XMLHttpRequest.DONE) { - this._logDebug( - '[EventSource][onreadystatechange][ERROR] Response status error.' - ); + this._logDebug('[EventSource][onreadystatechange][ERROR] Response status error.'); this._pollAgain(this.interval, false); } } @@ -229,16 +211,10 @@ class EventSource { if (this.lineEndingCharacter === null) { const detectedNewlineChar = this._detectNewlineChar(response); if (detectedNewlineChar !== null) { - this._logDebug( - `[EventSource] Automatically detected lineEndingCharacter: ${JSON.stringify( - detectedNewlineChar - ).slice(1, -1)}` - ); + this._logDebug(`[EventSource] Automatically detected lineEndingCharacter: ${JSON.stringify(detectedNewlineChar).slice(1, -1)}`); this.lineEndingCharacter = detectedNewlineChar; } else { - console.warn( - '[EventSource] Unable to identify the line ending character. Ensure your server delivers a standard line ending character: \\r\\n, \\n, \\r, or specify your custom character using the "lineEndingCharacter" option.' - ); + console.warn("[EventSource] Unable to identify the line ending character. Ensure your server delivers a standard line ending character: \\r\\n, \\n, \\r, or specify your custom character using the 'lineEndingCharacter' option."); return; } } @@ -248,9 +224,7 @@ class EventSource { return; } - const parts = response - .substring(this._lastIndexProcessed, indexOfDoubleNewline) - .split(this.lineEndingCharacter); + const parts = response.substring(this._lastIndexProcessed, indexOfDoubleNewline).split(this.lineEndingCharacter); this._lastIndexProcessed = indexOfDoubleNewline; let type = undefined; @@ -307,8 +281,7 @@ class EventSource { } _getLastDoubleNewlineIndex(response) { - const doubleLineEndingCharacter = - this.lineEndingCharacter + this.lineEndingCharacter; + const doubleLineEndingCharacter = this.lineEndingCharacter + this.lineEndingCharacter; const lastIndex = response.lastIndexOf(doubleLineEndingCharacter); if (lastIndex === -1) { return -1; @@ -327,9 +300,7 @@ class EventSource { removeEventListener(type, listener) { if (this.eventHandlers[type] !== undefined) { - this.eventHandlers[type] = this.eventHandlers[type].filter( - (handler) => handler !== listener - ); + this.eventHandlers[type] = this.eventHandlers[type].filter((handler) => handler !== listener); } } @@ -342,9 +313,7 @@ class EventSource { } } else { if (!availableTypes.includes(type)) { - throw Error( - `[EventSource] '${type}' type is not supported event type.` - ); + throw Error(`[EventSource] '${type}' type is not supported event type.`); } this.eventHandlers[type] = []; @@ -366,7 +335,6 @@ class EventSource { close() { if (this.status !== this.CLOSED) { this.status = this.CLOSED; - this._isActive = false; this.dispatch('close', { type: 'close' }); } From 1ace8ed6cf3a5e29d7a1ada1cc6531e51ac45f31 Mon Sep 17 00:00:00 2001 From: Ranjan Kumar Singh Date: Tue, 27 Aug 2024 02:36:05 +0530 Subject: [PATCH 3/3] minor update on readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 004d1c1..4bd10af 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ const options: EventSourceOptions = { debug: false, // Show console.debug messages for debugging purpose. Default: false pollingInterval: 5000, // Time (ms) between reconnections. If set to 0, reconnections will be disabled. Default: 5000 lineEndingCharacter: null // Character(s) used to represent line endings in received data. Common values: '\n' for LF (Unix/Linux), '\r\n' for CRLF (Windows), '\r' for CR (older Mac). Default: null (Automatically detect from event) - reconnectOnActive: true, // Automatically reconnect when the app becomes active (e.g., after being in the background) Default + reconnectOnActive: true, // Automatically reconnect when the app becomes active (e.g., after being in the background) Default: False } ```