Skip to content
Permalink
Newer
Older
100644 217 lines (181 sloc) 5.25 KB
April 13, 2016 16:58
1
/**
April 13, 2016 17:17
2
* Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>.
3
* All rights reserved.
April 13, 2016 16:58
4
*/
April 13, 2016 17:17
5
6
var SSE = function (url, options) {
April 13, 2016 16:58
7
if (!(this instanceof SSE)) {
8
return new SSE(url, options);
9
}
10
11
this.INITIALIZING = -1;
12
this.CONNECTING = 0;
13
this.OPEN = 1;
14
this.CLOSED = 2;
15
April 13, 2016 16:58
16
this.url = url;
17
18
options = options || {};
19
this.headers = options.headers || {};
20
this.payload = options.payload !== undefined ? options.payload : '';
21
this.method = options.method || (this.payload && 'POST' || 'GET');
22
this.withCredentials = !!options.withCredentials;
April 13, 2016 16:58
23
24
this.FIELD_SEPARATOR = ':';
April 13, 2016 16:58
25
this.listeners = {};
26
27
this.xhr = null;
28
this.readyState = this.INITIALIZING;
April 13, 2016 16:58
29
this.progress = 0;
April 13, 2016 16:58
31
32
this.addEventListener = function(type, listener) {
April 13, 2016 16:58
33
if (this.listeners[type] === undefined) {
34
this.listeners[type] = [];
35
}
36
37
if (this.listeners[type].indexOf(listener) === -1) {
38
this.listeners[type].push(listener);
39
}
April 13, 2016 16:58
40
};
41
42
this.removeEventListener = function(type, listener) {
43
if (this.listeners[type] === undefined) {
April 13, 2016 16:58
44
return;
45
}
46
47
var filtered = [];
48
this.listeners[type].forEach(function(element) {
49
if (element !== listener) {
50
filtered.push(element);
51
}
52
});
53
if (filtered.length === 0) {
54
delete this.listeners[type];
55
} else {
56
this.listeners[type] = filtered;
57
}
April 13, 2016 16:58
58
};
59
60
this.dispatchEvent = function(e) {
61
if (!e) {
62
return true;
63
}
64
66
67
var onHandler = 'on' + e.type;
68
if (this.hasOwnProperty(onHandler)) {
69
this[onHandler].call(this, e);
70
if (e.defaultPrevented) {
71
return false;
72
}
73
}
74
75
if (this.listeners[e.type]) {
76
return this.listeners[e.type].every(function(callback) {
77
callback(e);
78
return !e.defaultPrevented;
79
});
80
}
81
82
return true;
83
};
84
85
this._setReadyState = function(state) {
December 13, 2016 16:47
86
var event = new CustomEvent('readystatechange');
87
event.readyState = state;
88
this.readyState = state;
89
this.dispatchEvent(event);
90
};
91
92
this._onStreamFailure = function(e) {
93
var event = new CustomEvent('error');
September 14, 2022 18:46
94
event.data = e.currentTarget.response;
95
this.dispatchEvent(event);
96
this.close();
97
}
98
99
this._onStreamAbort = function(e) {
100
this.dispatchEvent(new CustomEvent('abort'));
101
this.close();
102
}
103
104
this._onStreamProgress = function(e) {
105
if (!this.xhr) {
106
return;
107
}
109
if (this.xhr.status !== 200) {
110
this._onStreamFailure(e);
111
return;
112
}
113
114
if (this.readyState == this.CONNECTING) {
December 13, 2016 16:47
115
this.dispatchEvent(new CustomEvent('open'));
116
this._setReadyState(this.OPEN);
117
}
118
April 13, 2016 16:58
119
var data = this.xhr.responseText.substring(this.progress);
120
this.progress += data.length;
121
data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) {
122
if (part.trim().length === 0) {
123
this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
124
this.chunk = '';
125
} else {
127
}
128
}.bind(this));
129
};
130
131
this._onStreamLoaded = function(e) {
132
this._onStreamProgress(e);
133
134
// Parse the last chunk.
135
this.dispatchEvent(this._parseEventChunk(this.chunk));
136
this.chunk = '';
137
};
138
139
/**
140
* Parse a received SSE event chunk into a constructed event object.
141
*/
142
this._parseEventChunk = function(chunk) {
143
if (!chunk || chunk.length === 0) {
144
return null;
145
}
146
147
var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'};
148
chunk.split(/\n|\r\n|\r/).forEach(function(line) {
149
line = line.trimRight();
150
var index = line.indexOf(this.FIELD_SEPARATOR);
151
if (index <= 0) {
152
// Line was either empty, or started with a separator and is a comment.
153
// Either way, ignore.
154
return;
155
}
156
157
var field = line.substring(0, index);
158
if (!(field in e)) {
159
return;
160
}
161
162
var value = line.substring(index + 1).trimLeft();
163
if (field === 'data') {
164
e[field] += value;
165
} else {
166
e[field] = value;
167
}
April 13, 2016 16:58
168
}.bind(this));
December 13, 2016 16:47
170
var event = new CustomEvent(e.event);
171
event.data = e.data;
172
event.id = e.id;
173
return event;
April 13, 2016 16:58
174
};
175
176
this._checkStreamClosed = function() {
177
if (!this.xhr) {
178
return;
179
}
180
181
if (this.xhr.readyState === XMLHttpRequest.DONE) {
182
this._setReadyState(this.CLOSED);
183
}
184
};
185
April 13, 2016 16:58
186
this.stream = function() {
187
this._setReadyState(this.CONNECTING);
188
April 13, 2016 16:58
189
this.xhr = new XMLHttpRequest();
190
this.xhr.addEventListener('progress', this._onStreamProgress.bind(this));
191
this.xhr.addEventListener('load', this._onStreamLoaded.bind(this));
192
this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this));
193
this.xhr.addEventListener('error', this._onStreamFailure.bind(this));
194
this.xhr.addEventListener('abort', this._onStreamAbort.bind(this));
April 13, 2016 16:58
195
this.xhr.open(this.method, this.url);
196
for (var header in this.headers) {
197
this.xhr.setRequestHeader(header, this.headers[header]);
198
}
199
this.xhr.withCredentials = this.withCredentials;
April 13, 2016 16:58
200
this.xhr.send(this.payload);
201
};
202
203
this.close = function() {
204
if (this.readyState === this.CLOSED) {
205
return;
206
}
207
208
this.xhr.abort();
209
this.xhr = null;
210
this._setReadyState(this.CLOSED);
211
};
212
};
213
214
// Export our SSE module for npm.js
215
if (typeof exports !== 'undefined') {
216
exports.SSE = SSE;
April 13, 2016 16:58
217
}