Skip to content

Commit 1782a9f

Browse files
authored
Merge pull request #10 from Salesforce-Async-Messaging/u/bdrasin/W-20451274/retru_on_sse_error
w-20451274 retry_on_sse_error
2 parents e57cd48 + 42bd8ca commit 1782a9f

File tree

1 file changed

+103
-16
lines changed

1 file changed

+103
-16
lines changed

src/services/eventSourceService.js

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import { getOrganizationId, getSalesforceMessagingUrl, getJwt, getLastEventId }
55
*/
66
let eventSource;
77

8+
/**
9+
* Configuration for reconnection behavior
10+
*/
11+
const RECONNECT_CONFIG = {
12+
maxAttempts: 10,
13+
initialDelay: 1000, // 1 second
14+
maxDelay: 30000, // 30 seconds
15+
backoffMultiplier: 1.5
16+
};
17+
818
/**
919
* Get the request headers for connecting to SSE.
1020
*/
@@ -51,6 +61,17 @@ const handleEventSourceListeners = (listenerOperationName, eventListenerMap) =>
5161
});
5262
};
5363

64+
/**
65+
* Calculate the delay for the next reconnection attempt using exponential backoff.
66+
*
67+
* @param {Number} attemptNumber - The current attempt number (0-indexed)
68+
* @returns {Number} Delay in milliseconds
69+
*/
70+
const calculateReconnectDelay = (attemptNumber) => {
71+
const delay = RECONNECT_CONFIG.initialDelay * Math.pow(RECONNECT_CONFIG.backoffMultiplier, attemptNumber);
72+
return Math.min(delay, RECONNECT_CONFIG.maxDelay);
73+
};
74+
5475
/**
5576
* Establish the EventSource object with handlers for onopen and onerror.
5677
*
@@ -70,26 +91,92 @@ export const createEventSource = (fullApiPath, eventListenerMap) => {
7091
}
7192

7293
/**
73-
* Create a closure here to isolate `reconnectAttempts` and `reconnectIntervalSeconds` values to a single invocation of `createEventSource`.
74-
* All calls to `resolveEventSource` within a call to `createEventSource` share the same `reconnectAttempts` and `reconnectIntervalSeconds` values.
94+
* Create a closure here to isolate `reconnectAttempts` and reconnect logic to a single invocation of `createEventSource`.
95+
* All calls to `resolveEventSource` within a call to `createEventSource` share the same `reconnectAttempts` value.
7596
*
7697
* @param {Promise.resolve} resolve - Event source opened.
7798
* @param {Promise.reject} reject - Attempted to reconnect to event source too many times.
7899
*/
79100
const resolveEventSource = (resolve, reject) => {
80-
try {
81-
eventSource = new window.EventSourcePolyfill(fullApiPath, getEventSourceParams());
82-
83-
eventSource.onopen = () => {
84-
handleEventSourceListeners("addEventListener", eventListenerMap);
85-
resolve();
86-
};
87-
eventSource.onerror = (error) => {
88-
reject();
89-
};
90-
} catch (error) {
91-
reject(error);
92-
}
101+
let reconnectAttempts = 0;
102+
let reconnectTimeoutId = null;
103+
104+
/**
105+
* Attempts to create a new EventSource connection.
106+
* This function is called initially and on each reconnection attempt.
107+
*/
108+
const attemptConnection = () => {
109+
try {
110+
// Close existing eventSource if it exists
111+
if (eventSource) {
112+
try {
113+
eventSource.close();
114+
} catch (closeError) {
115+
// Ignore errors when closing, as the connection may already be closed
116+
console.warn("Error closing existing EventSource:", closeError);
117+
}
118+
}
119+
120+
// Create new EventSource with timestamp to avoid caching
121+
const apiPathWithTimestamp = `${fullApiPath}${fullApiPath.includes('?') ? '&' : '?'}_ts=${Date.now()}`;
122+
eventSource = new window.EventSourcePolyfill(apiPathWithTimestamp, getEventSourceParams());
123+
124+
eventSource.onopen = () => {
125+
// Reset reconnect attempts on successful connection
126+
reconnectAttempts = 0;
127+
if (reconnectTimeoutId) {
128+
clearTimeout(reconnectTimeoutId);
129+
reconnectTimeoutId = null;
130+
}
131+
handleEventSourceListeners("addEventListener", eventListenerMap);
132+
resolve();
133+
};
134+
135+
eventSource.onerror = (error) => {
136+
// Remove event listeners to prevent duplicates, then attenpt to reconnect
137+
handleEventSourceListeners("removeEventListener", eventListenerMap);
138+
reconnectAttempts++;
139+
140+
if (reconnectAttempts <= RECONNECT_CONFIG.maxAttempts) {
141+
const delay = calculateReconnectDelay(reconnectAttempts - 1);
142+
console.log(`EventSource connection error. Attempting to reconnect (${reconnectAttempts}/${RECONNECT_CONFIG.maxAttempts}) in ${delay}ms...`);
143+
144+
reconnectTimeoutId = setTimeout(() => {
145+
attemptConnection();
146+
}, delay);
147+
} else {
148+
console.error(`EventSource connection failed after ${RECONNECT_CONFIG.maxAttempts} reconnection attempts.`);
149+
if (reconnectTimeoutId) {
150+
clearTimeout(reconnectTimeoutId);
151+
reconnectTimeoutId = null;
152+
}
153+
reject(new Error(`Failed to establish EventSource connection after ${RECONNECT_CONFIG.maxAttempts} attempts`));
154+
}
155+
156+
};
157+
} catch (error) {
158+
reconnectAttempts++;
159+
160+
if (reconnectAttempts <= RECONNECT_CONFIG.maxAttempts) {
161+
const delay = calculateReconnectDelay(reconnectAttempts - 1);
162+
console.log(`Error creating EventSource. Attempting to reconnect (${reconnectAttempts}/${RECONNECT_CONFIG.maxAttempts}) in ${delay}ms...`);
163+
164+
reconnectTimeoutId = setTimeout(() => {
165+
attemptConnection();
166+
}, delay);
167+
} else {
168+
console.error(`Failed to create EventSource after ${RECONNECT_CONFIG.maxAttempts} attempts.`);
169+
if (reconnectTimeoutId) {
170+
clearTimeout(reconnectTimeoutId);
171+
reconnectTimeoutId = null;
172+
}
173+
reject(error);
174+
}
175+
}
176+
};
177+
178+
// Start the initial connection attempt
179+
attemptConnection();
93180
};
94181

95182
return new Promise(resolveEventSource);
@@ -114,7 +201,7 @@ export const subscribeToEventSource = (eventListenerMap) => {
114201
* Directly connect to event router endpoint on Salesforce Messaging domain instead of going through ia-message.
115202
*/
116203
createEventSource(
117-
getSalesforceMessagingUrl().concat(`/eventrouter/v1/sse?_ts=${Date.now()}`),
204+
getSalesforceMessagingUrl().concat(`/eventrouter/v1/sse`),
118205
eventListenerMap
119206
).then(
120207
resolve,

0 commit comments

Comments
 (0)