Skip to content

Commit e7199b9

Browse files
authored
Fix closing of connection, address various minor issues and code smells, improve options and overall usability (#27)
* Fix timeout option camelCase * Improve config descriptions in Readme * Align config options order in code with Readme * Change || to ?? for all numeric config options * Move logging to dedicated _logDebug function * Disable reconnections if pollingInterval is 0 * Fix set ERROR status on error * Put event in single line when dispatching timeout * Fix whitespace * Remove pointless check for xhr.readyState UNSENT Note that readyState can only be DONE or LOADING at this point anyway * Prevent activity after connection is closed Always leave xhr change/error handlers immediately when status is CLOSED Fixes #24 * Align config options order in type def with Readme * Add withCredentials option * Fix syntax of example options object
1 parent 76dc55a commit e7199b9

File tree

3 files changed

+57
-51
lines changed

3 files changed

+57
-51
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,14 @@ new EventSource(url: string | URL, options?: EventSourceOptions);
185185

186186
```typescript
187187
const options: EventSourceOptions = {
188-
method: 'GET'; // Request method. Default: GET
189-
timeout: 0; // Time after which the connection will expire without any activity: Default: 0 (no timeout)
190-
timeoutBeforeConnection: 500; // Time to wait before initial connection is made: Default: 500ms
191-
headers: {}; // Your request headers. Default: {}
192-
body: undefined; // Your request body sent on connection: Default: undefined
193-
debug: false; // Show console.debug messages for debugging purpose. Default: false
194-
pollingInterval: 5000; // Time (ms) between reconnections. Default: 5000
188+
method: 'GET', // Request method. Default: GET
189+
timeout: 0, // Time (ms) after which the connection will expire without any activity. Default: 0 (no timeout)
190+
timeoutBeforeConnection: 500, // Time (ms) to wait before initial connection is made. Default: 500
191+
withCredentials: false, // Include credentials in cross-site Access-Control requests. Default: false
192+
headers: {}, // Your request headers. Default: {}
193+
body: undefined, // Your request body sent on connection. Default: undefined
194+
debug: false, // Show console.debug messages for debugging purpose. Default: false
195+
pollingInterval: 5000, // Time (ms) between reconnections. If set to 0, reconnections will be disabled. Default: 5000
195196
}
196197
```
197198

index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ export interface ExceptionEvent {
4646
export interface EventSourceOptions {
4747
method?: string;
4848
timeout?: number;
49+
timeoutBeforeConnection?: number;
50+
withCredentials?: boolean;
4951
headers?: Record<string, any>;
5052
body?: any;
5153
debug?: boolean;
5254
pollingInterval?: number;
53-
timeoutBeforeConnection?: number;
5455
}
5556

5657
export type EventSourceEvent = MessageEvent | OpenEvent | CloseEvent | TimeoutEvent | ErrorEvent | ExceptionEvent;

src/EventSource.js

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ class EventSource {
55
CLOSED = 2;
66

77
constructor(url, options = {}) {
8-
this.interval = options.pollingInterval || 5000;
98
this.lastEventId = null;
109
this.lastIndexProcessed = 0;
1110
this.eventType = undefined;
@@ -19,11 +18,13 @@ class EventSource {
1918
};
2019

2120
this.method = options.method || 'GET';
22-
this.timeout = options.timeOut || 0;
21+
this.timeout = options.timeout ?? 0;
22+
this.timeoutBeforeConnection = options.timeoutBeforeConnection ?? 500;
23+
this.withCredentials = options.withCredentials || false;
2324
this.headers = options.headers || {};
2425
this.body = options.body || undefined;
2526
this.debug = options.debug || false;
26-
this.timeoutBeforeConnection = options.timeoutBeforeConnection ?? 500;
27+
this.interval = options.pollingInterval ?? 5000;
2728

2829
this._xhr = null;
2930
this._pollTimer = null;
@@ -38,13 +39,16 @@ class EventSource {
3839
this.url = url;
3940
}
4041

41-
this._pollAgain(this.timeoutBeforeConnection);
42+
this._pollAgain(this.timeoutBeforeConnection, true);
4243
}
4344

44-
_pollAgain(time) {
45-
this._pollTimer = setTimeout(() => {
46-
this.open();
47-
}, time);
45+
_pollAgain(time, allowZero) {
46+
if (time > 0 || allowZero) {
47+
this._logDebug(`[EventSource] Will open new connection in ${time} ms.`);
48+
this._pollTimer = setTimeout(() => {
49+
this.open();
50+
}, time);
51+
}
4852
}
4953

5054
open() {
@@ -55,6 +59,10 @@ class EventSource {
5559
this._xhr = new XMLHttpRequest();
5660
this._xhr.open(this.method, this.url, true);
5761

62+
if (this.withCredentials) {
63+
this._xhr.withCredentials = true;
64+
}
65+
5866
if (this.headers) {
5967
for (const [key, value] of Object.entries(this.headers)) {
6068
this._xhr.setRequestHeader(key, value);
@@ -72,13 +80,13 @@ class EventSource {
7280
this._xhr.timeout = this.timeout;
7381

7482
this._xhr.onreadystatechange = () => {
83+
if (this.status === this.CLOSED) {
84+
return;
85+
}
86+
7587
const xhr = this._xhr;
7688

77-
if (this.debug) {
78-
console.debug(
79-
`[EventSource][onreadystatechange] ReadyState: ${xhr.readyState}, status: ${xhr.status}`
80-
);
81-
}
89+
this._logDebug(`[EventSource][onreadystatechange] ReadyState: ${xhr.readyState}, status: ${xhr.status}`);
8290

8391
if (![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes(xhr.readyState)) {
8492
return;
@@ -93,38 +101,31 @@ class EventSource {
93101
this._handleEvent(xhr.responseText || '');
94102

95103
if (xhr.readyState === XMLHttpRequest.DONE) {
96-
if (this.debug) {
97-
console.debug(
98-
'[EventSource][onreadystatechange][DONE] Operation done. Reconnecting...'
99-
);
100-
}
101-
this._pollAgain(this.interval);
102-
}
103-
} else if (this.status !== this.CLOSED) {
104-
if (this._xhr.status !== 0) {
105-
this.dispatch('error', {
106-
type: 'error',
107-
message: xhr.responseText,
108-
xhrStatus: xhr.status,
109-
xhrState: xhr.readyState,
110-
});
104+
this._logDebug('[EventSource][onreadystatechange][DONE] Operation done.');
105+
this._pollAgain(this.interval, false);
111106
}
107+
} else if (xhr.status !== 0) {
108+
this.status = this.ERROR;
109+
this.dispatch('error', {
110+
type: 'error',
111+
message: xhr.responseText,
112+
xhrStatus: xhr.status,
113+
xhrState: xhr.readyState,
114+
});
112115

113-
if ([XMLHttpRequest.DONE, XMLHttpRequest.UNSENT].includes(xhr.readyState)) {
114-
if (this.debug) {
115-
console.debug(
116-
'[EventSource][onreadystatechange][ERROR] Response status error. Reconnecting...'
117-
);
118-
}
119-
120-
this._pollAgain(this.interval);
116+
if (xhr.readyState === XMLHttpRequest.DONE) {
117+
this._logDebug('[EventSource][onreadystatechange][ERROR] Response status error.');
118+
this._pollAgain(this.interval, false);
121119
}
122120
}
123121
};
124122

125-
this._xhr.onerror = (e) => {
126-
this.status === this.ERROR;
123+
this._xhr.onerror = () => {
124+
if (this.status === this.CLOSED) {
125+
return;
126+
}
127127

128+
this.status = this.ERROR;
128129
this.dispatch('error', {
129130
type: 'error',
130131
message: this._xhr.responseText,
@@ -142,10 +143,7 @@ class EventSource {
142143
if (this.timeout > 0) {
143144
setTimeout(() => {
144145
if (this._xhr.readyState === XMLHttpRequest.LOADING) {
145-
this.dispatch('error', {
146-
type: 'timeout',
147-
});
148-
146+
this.dispatch('error', { type: 'timeout' });
149147
this.close();
150148
}
151149
}, this.timeout);
@@ -160,6 +158,12 @@ class EventSource {
160158
}
161159
}
162160

161+
_logDebug(...msg) {
162+
if (this.debug) {
163+
console.debug(...msg);
164+
}
165+
}
166+
163167
_handleEvent(response) {
164168
const parts = response.substr(this.lastIndexProcessed).split('\n');
165169
this.lastIndexProcessed = response.lastIndexOf('\n\n') + 2;
@@ -205,7 +209,7 @@ class EventSource {
205209
if (this.eventHandlers[type] === undefined) {
206210
this.eventHandlers[type] = [];
207211
}
208-
212+
209213
this.eventHandlers[type].push(listener);
210214
}
211215

0 commit comments

Comments
 (0)