├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── dist ├── vue-sse.common.js ├── vue-sse.esm.browser.js ├── vue-sse.esm.browser.min.js ├── vue-sse.esm.js ├── vue-sse.js ├── vue-sse.min.js └── vue-sse.mjs ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── index.cjs.js ├── index.js ├── sse-client.js └── sse-manager.js ├── test ├── .eslintrc.json ├── setup.js ├── sse-client.spec.js └── sse-manager.spec.js └── types ├── index.d.ts └── vue.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.js] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plugin:vue/recommended" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | 4 | # SublimeText 5 | *.sublime-project 6 | *.sublime-workspace 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Churchard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueSSE 2 | [![GitHub issues](https://img.shields.io/github/issues/tserkov/vue-sse.svg)]() 3 | [![license](https://img.shields.io/github/license/tserkov/vue-sse.svg)]() 4 | 5 | VueSSE enables effortless use of [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) by providing a high-level interface to an underlying [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). 6 | 7 | ## Install 8 | ```bash 9 | # npm 10 | npm install --save vue-sse 11 | 12 | # OR yarn 13 | yarn add vue-sse 14 | ``` 15 | 16 | ```javascript 17 | // in main.js 18 | import VueSSE from 'vue-sse'; 19 | 20 | // using defaults 21 | Vue.use(VueSSE); 22 | 23 | // OR specify custom defaults (described below) 24 | Vue.use(VueSSE, { 25 | format: 'json', 26 | polyfill: true, 27 | url: '/my-events-server', 28 | withCredentials: true, 29 | }); 30 | ``` 31 | 32 | ## Quickstart 33 | ```js 34 | this.$sse.create('/my-events-server') 35 | .on('message', (msg) => console.info('Message:', msg)) 36 | .on('error', (err) => console.error('Failed to parse or lost connection:', err)) 37 | .connect() 38 | .catch((err) => console.error('Failed make initial connection:', err)); 39 | ``` 40 | 41 | ## Usage 42 | Clients can be created from the Vue object via `Vue.$sse.create(...)` or from within components via `this.$sse.create(...)` 43 | 44 | All of the following are valid calls to create a client: 45 | - `this.$sse.create('/your-events-endpoint')` to connect to the specified URL without specifying any config 46 | - `this.$sse.create({ url: '/your-events-endpoint', format: 'json' })` will automatically parse incoming messages as JSON 47 | - `this.$sse.create({ url: '/your-events-endpoint', withCredentials: true })` will set CORS on the request 48 | 49 | Once you've created a client, you can add handlers before or after calling `connect()`, which must be called. 50 | 51 | ## Configuration 52 | `$sse.create` accepts the following config options when installing VueSSE via `Vue.use` and when calling `$sse.create`. 53 | 54 | | Option | Type | Description | Default | 55 | | --- | --- | --- | -- | 56 | | format | `"plain"` \| `"json"` \| `(event: MessageEvent) => any` | Specify pre-processing, if any, to perform on incoming messages. Messages that fail formatting will emit an error. | `"plain"` | 57 | | url | `string` | The location of the SSE server. | `""` | 58 | | withCredentials | `boolean` | Indicates if CORS should be set to include credentials. | `false` | 59 | | polyfill | `boolean` | Include an [EventSource polyfill](https://github.com/Yaffle/EventSource) for older browsers. | `false` | 60 | | forcePolyfill | `boolean` | Forces the [EventSource polyfill](https://github.com/Yaffle/EventSource) to always be used over native. | `false` | 61 | | polyfillOptions | `object` | Custom options to provide to the [EventSource polyfill](https://github.com/Yaffle/EventSource#custom-headers). Only used if `forcePolyfill` is true. | `null` | 62 | 63 | If `$sse.create` is called with a string, it must be the URL to the SSE server. 64 | 65 | ## Methods 66 | Once you've successfully connected to an events server, a client will be returned with the following methods: 67 | 68 | | Name | Description | 69 | | --- | --- | 70 | | __connect__(): _Promise_ | Connects to the server. __Must be called.__ | 71 | | __on__(event: _string_, (data: _any_) => _void_): _SSEClient_ | Adds an event-specific listener to the event stream. The handler function receives the message as its argument (formatted if a format was specified), and the original underlying Event. For non-event messages, specify `""` or `"message"` as the event. | 72 | | __once__(event: _string_, (data: _any_) => _void_): _SSEClient_ | Same as `on(...)`, but only triggered once. | 73 | | __off__(event: _string_, (data: _any_ => _void_)): _SSEClient_ | Removes the given handler from the event stream. The function must be the same as provided to `on`/`once`. | 74 | | __on__('error', (err) => void): _SSEClient_ | Allows your application to handle any errors thrown, such as loss of server connection and pre-processing errors. | 75 | | __disconnect__(): _void_ | Closes the connection. The client can be re-used by calling `connect()`. __Must be called!__ (Usually, in the `beforeDestroy` of your component.) | 76 | 77 | ## Properties 78 | | Name | Type | Description | 79 | | --- | --- | --- | 80 | | source | `EventSource` | Returns the underlying EventSource. | 81 | 82 | ## Cleanup 83 | Every connection must be disconnected when the component is destroyed. There are two ways to achieve this: 84 | 1. Call `disconnect()` on the client during `beforeDestroy`, or 85 | 2. Add the following option to your component to have them automatically closed for you during `beforeDestroy`: 86 | ```js 87 | export default { 88 | name: 'my-component', 89 | data() { /* ... */ }, 90 | // ... 91 | sse: { 92 | cleanup: true, 93 | }, 94 | // ... 95 | } 96 | ``` 97 | 98 | ## Vue 3 99 | This plugin works the same in both Vue 2 and 3. The Composition API is not yet supported. 100 | 101 | ## Example 102 | An example project is provided at [tserkov/vue-sse-example](https://github.com/tserkov/vue-sse-example). 103 | 104 | ### Kitchen Sink 105 | ```html 106 | 114 | 115 | 200 | ``` 201 | -------------------------------------------------------------------------------- /dist/vue-sse.common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-sse v2.5.0 3 | * (c) 2021 James Churchard 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 9 | 10 | function createCommonjsModule(fn) { 11 | var module = { exports: {} }; 12 | return fn(module, module.exports), module.exports; 13 | } 14 | 15 | /** @license 16 | * eventsource.js 17 | * Available under MIT License (MIT) 18 | * https://github.com/Yaffle/EventSource/ 19 | */ 20 | 21 | var eventsource = createCommonjsModule(function (module, exports) { 22 | /*jslint indent: 2, vars: true, plusplus: true */ 23 | /*global setTimeout, clearTimeout */ 24 | 25 | (function (global) { 26 | 27 | var setTimeout = global.setTimeout; 28 | var clearTimeout = global.clearTimeout; 29 | var XMLHttpRequest = global.XMLHttpRequest; 30 | var XDomainRequest = global.XDomainRequest; 31 | var ActiveXObject = global.ActiveXObject; 32 | var NativeEventSource = global.EventSource; 33 | 34 | var document = global.document; 35 | var Promise = global.Promise; 36 | var fetch = global.fetch; 37 | var Response = global.Response; 38 | var TextDecoder = global.TextDecoder; 39 | var TextEncoder = global.TextEncoder; 40 | var AbortController = global.AbortController; 41 | 42 | if (typeof window !== "undefined" && !("readyState" in document) && document.body == null) { // Firefox 2 43 | document.readyState = "loading"; 44 | window.addEventListener("load", function (event) { 45 | document.readyState = "complete"; 46 | }, false); 47 | } 48 | 49 | if (XMLHttpRequest == null && ActiveXObject != null) { // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest_in_IE6 50 | XMLHttpRequest = function () { 51 | return new ActiveXObject("Microsoft.XMLHTTP"); 52 | }; 53 | } 54 | 55 | if (Object.create == undefined) { 56 | Object.create = function (C) { 57 | function F(){} 58 | F.prototype = C; 59 | return new F(); 60 | }; 61 | } 62 | 63 | if (!Date.now) { 64 | Date.now = function now() { 65 | return new Date().getTime(); 66 | }; 67 | } 68 | 69 | // see #118 (Promise#finally with polyfilled Promise) 70 | // see #123 (data URLs crash Edge) 71 | // see #125 (CSP violations) 72 | // see pull/#138 73 | // => No way to polyfill Promise#finally 74 | 75 | if (AbortController == undefined) { 76 | var originalFetch2 = fetch; 77 | fetch = function (url, options) { 78 | var signal = options.signal; 79 | return originalFetch2(url, {headers: options.headers, credentials: options.credentials, cache: options.cache}).then(function (response) { 80 | var reader = response.body.getReader(); 81 | signal._reader = reader; 82 | if (signal._aborted) { 83 | signal._reader.cancel(); 84 | } 85 | return { 86 | status: response.status, 87 | statusText: response.statusText, 88 | headers: response.headers, 89 | body: { 90 | getReader: function () { 91 | return reader; 92 | } 93 | } 94 | }; 95 | }); 96 | }; 97 | AbortController = function () { 98 | this.signal = { 99 | _reader: null, 100 | _aborted: false 101 | }; 102 | this.abort = function () { 103 | if (this.signal._reader != null) { 104 | this.signal._reader.cancel(); 105 | } 106 | this.signal._aborted = true; 107 | }; 108 | }; 109 | } 110 | 111 | function TextDecoderPolyfill() { 112 | this.bitsNeeded = 0; 113 | this.codePoint = 0; 114 | } 115 | 116 | TextDecoderPolyfill.prototype.decode = function (octets) { 117 | function valid(codePoint, shift, octetsCount) { 118 | if (octetsCount === 1) { 119 | return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07FF; 120 | } 121 | if (octetsCount === 2) { 122 | return codePoint >= 0x0800 >> shift && codePoint << shift <= 0xD7FF || codePoint >= 0xE000 >> shift && codePoint << shift <= 0xFFFF; 123 | } 124 | if (octetsCount === 3) { 125 | return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10FFFF; 126 | } 127 | throw new Error(); 128 | } 129 | function octetsCount(bitsNeeded, codePoint) { 130 | if (bitsNeeded === 6 * 1) { 131 | return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1; 132 | } 133 | if (bitsNeeded === 6 * 2) { 134 | return codePoint > 15 ? 3 : 2; 135 | } 136 | if (bitsNeeded === 6 * 3) { 137 | return 3; 138 | } 139 | throw new Error(); 140 | } 141 | var REPLACER = 0xFFFD; 142 | var string = ""; 143 | var bitsNeeded = this.bitsNeeded; 144 | var codePoint = this.codePoint; 145 | for (var i = 0; i < octets.length; i += 1) { 146 | var octet = octets[i]; 147 | if (bitsNeeded !== 0) { 148 | if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) { 149 | bitsNeeded = 0; 150 | codePoint = REPLACER; 151 | string += String.fromCharCode(codePoint); 152 | } 153 | } 154 | if (bitsNeeded === 0) { 155 | if (octet >= 0 && octet <= 127) { 156 | bitsNeeded = 0; 157 | codePoint = octet; 158 | } else if (octet >= 192 && octet <= 223) { 159 | bitsNeeded = 6 * 1; 160 | codePoint = octet & 31; 161 | } else if (octet >= 224 && octet <= 239) { 162 | bitsNeeded = 6 * 2; 163 | codePoint = octet & 15; 164 | } else if (octet >= 240 && octet <= 247) { 165 | bitsNeeded = 6 * 3; 166 | codePoint = octet & 7; 167 | } else { 168 | bitsNeeded = 0; 169 | codePoint = REPLACER; 170 | } 171 | if (bitsNeeded !== 0 && !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint))) { 172 | bitsNeeded = 0; 173 | codePoint = REPLACER; 174 | } 175 | } else { 176 | bitsNeeded -= 6; 177 | codePoint = codePoint << 6 | octet & 63; 178 | } 179 | if (bitsNeeded === 0) { 180 | if (codePoint <= 0xFFFF) { 181 | string += String.fromCharCode(codePoint); 182 | } else { 183 | string += String.fromCharCode(0xD800 + (codePoint - 0xFFFF - 1 >> 10)); 184 | string += String.fromCharCode(0xDC00 + (codePoint - 0xFFFF - 1 & 0x3FF)); 185 | } 186 | } 187 | } 188 | this.bitsNeeded = bitsNeeded; 189 | this.codePoint = codePoint; 190 | return string; 191 | }; 192 | 193 | // Firefox < 38 throws an error with stream option 194 | var supportsStreamOption = function () { 195 | try { 196 | return new TextDecoder().decode(new TextEncoder().encode("test"), {stream: true}) === "test"; 197 | } catch (error) { 198 | console.debug("TextDecoder does not support streaming option. Using polyfill instead: " + error); 199 | } 200 | return false; 201 | }; 202 | 203 | // IE, Edge 204 | if (TextDecoder == undefined || TextEncoder == undefined || !supportsStreamOption()) { 205 | TextDecoder = TextDecoderPolyfill; 206 | } 207 | 208 | var k = function () { 209 | }; 210 | 211 | function XHRWrapper(xhr) { 212 | this.withCredentials = false; 213 | this.readyState = 0; 214 | this.status = 0; 215 | this.statusText = ""; 216 | this.responseText = ""; 217 | this.onprogress = k; 218 | this.onload = k; 219 | this.onerror = k; 220 | this.onreadystatechange = k; 221 | this._contentType = ""; 222 | this._xhr = xhr; 223 | this._sendTimeout = 0; 224 | this._abort = k; 225 | } 226 | 227 | XHRWrapper.prototype.open = function (method, url) { 228 | this._abort(true); 229 | 230 | var that = this; 231 | var xhr = this._xhr; 232 | var state = 1; 233 | var timeout = 0; 234 | 235 | this._abort = function (silent) { 236 | if (that._sendTimeout !== 0) { 237 | clearTimeout(that._sendTimeout); 238 | that._sendTimeout = 0; 239 | } 240 | if (state === 1 || state === 2 || state === 3) { 241 | state = 4; 242 | xhr.onload = k; 243 | xhr.onerror = k; 244 | xhr.onabort = k; 245 | xhr.onprogress = k; 246 | xhr.onreadystatechange = k; 247 | // IE 8 - 9: XDomainRequest#abort() does not fire any event 248 | // Opera < 10: XMLHttpRequest#abort() does not fire any event 249 | xhr.abort(); 250 | if (timeout !== 0) { 251 | clearTimeout(timeout); 252 | timeout = 0; 253 | } 254 | if (!silent) { 255 | that.readyState = 4; 256 | that.onabort(null); 257 | that.onreadystatechange(); 258 | } 259 | } 260 | state = 0; 261 | }; 262 | 263 | var onStart = function () { 264 | if (state === 1) { 265 | //state = 2; 266 | var status = 0; 267 | var statusText = ""; 268 | var contentType = undefined; 269 | if (!("contentType" in xhr)) { 270 | try { 271 | status = xhr.status; 272 | statusText = xhr.statusText; 273 | contentType = xhr.getResponseHeader("Content-Type"); 274 | } catch (error) { 275 | // IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3 276 | // Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2 277 | // https://bugs.webkit.org/show_bug.cgi?id=29121 278 | status = 0; 279 | statusText = ""; 280 | contentType = undefined; 281 | // Firefox < 14, Chrome ?, Safari ? 282 | // https://bugs.webkit.org/show_bug.cgi?id=29658 283 | // https://bugs.webkit.org/show_bug.cgi?id=77854 284 | } 285 | } else { 286 | status = 200; 287 | statusText = "OK"; 288 | contentType = xhr.contentType; 289 | } 290 | if (status !== 0) { 291 | state = 2; 292 | that.readyState = 2; 293 | that.status = status; 294 | that.statusText = statusText; 295 | that._contentType = contentType; 296 | that.onreadystatechange(); 297 | } 298 | } 299 | }; 300 | var onProgress = function () { 301 | onStart(); 302 | if (state === 2 || state === 3) { 303 | state = 3; 304 | var responseText = ""; 305 | try { 306 | responseText = xhr.responseText; 307 | } catch (error) { 308 | // IE 8 - 9 with XMLHttpRequest 309 | } 310 | that.readyState = 3; 311 | that.responseText = responseText; 312 | that.onprogress(); 313 | } 314 | }; 315 | var onFinish = function (type, event) { 316 | if (event == null || event.preventDefault == null) { 317 | event = { 318 | preventDefault: k 319 | }; 320 | } 321 | // Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3) 322 | // IE 8 fires "onload" without "onprogress" 323 | onProgress(); 324 | if (state === 1 || state === 2 || state === 3) { 325 | state = 4; 326 | if (timeout !== 0) { 327 | clearTimeout(timeout); 328 | timeout = 0; 329 | } 330 | that.readyState = 4; 331 | if (type === "load") { 332 | that.onload(event); 333 | } else if (type === "error") { 334 | that.onerror(event); 335 | } else if (type === "abort") { 336 | that.onabort(event); 337 | } else { 338 | throw new TypeError(); 339 | } 340 | that.onreadystatechange(); 341 | } 342 | }; 343 | var onReadyStateChange = function (event) { 344 | if (xhr != undefined) { // Opera 12 345 | if (xhr.readyState === 4) { 346 | if (!("onload" in xhr) || !("onerror" in xhr) || !("onabort" in xhr)) { 347 | onFinish(xhr.responseText === "" ? "error" : "load", event); 348 | } 349 | } else if (xhr.readyState === 3) { 350 | if (!("onprogress" in xhr)) { // testing XMLHttpRequest#responseText too many times is too slow in IE 11 351 | // and in Firefox 3.6 352 | onProgress(); 353 | } 354 | } else if (xhr.readyState === 2) { 355 | onStart(); 356 | } 357 | } 358 | }; 359 | var onTimeout = function () { 360 | timeout = setTimeout(function () { 361 | onTimeout(); 362 | }, 500); 363 | if (xhr.readyState === 3) { 364 | onProgress(); 365 | } 366 | }; 367 | 368 | // XDomainRequest#abort removes onprogress, onerror, onload 369 | if ("onload" in xhr) { 370 | xhr.onload = function (event) { 371 | onFinish("load", event); 372 | }; 373 | } 374 | if ("onerror" in xhr) { 375 | xhr.onerror = function (event) { 376 | onFinish("error", event); 377 | }; 378 | } 379 | // improper fix to match Firefox behaviour, but it is better than just ignore abort 380 | // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596 381 | // https://bugzilla.mozilla.org/show_bug.cgi?id=880200 382 | // https://code.google.com/p/chromium/issues/detail?id=153570 383 | // IE 8 fires "onload" without "onprogress 384 | if ("onabort" in xhr) { 385 | xhr.onabort = function (event) { 386 | onFinish("abort", event); 387 | }; 388 | } 389 | 390 | if ("onprogress" in xhr) { 391 | xhr.onprogress = onProgress; 392 | } 393 | 394 | // IE 8 - 9 (XMLHTTPRequest) 395 | // Opera < 12 396 | // Firefox < 3.5 397 | // Firefox 3.5 - 3.6 - ? < 9.0 398 | // onprogress is not fired sometimes or delayed 399 | // see also #64 (significant lag in IE 11) 400 | if ("onreadystatechange" in xhr) { 401 | xhr.onreadystatechange = function (event) { 402 | onReadyStateChange(event); 403 | }; 404 | } 405 | 406 | if ("contentType" in xhr || !("ontimeout" in XMLHttpRequest.prototype)) { 407 | url += (url.indexOf("?") === -1 ? "?" : "&") + "padding=true"; 408 | } 409 | xhr.open(method, url, true); 410 | 411 | if ("readyState" in xhr) { 412 | // workaround for Opera 12 issue with "progress" events 413 | // #91 (XMLHttpRequest onprogress not fired for streaming response in Edge 14-15-?) 414 | timeout = setTimeout(function () { 415 | onTimeout(); 416 | }, 0); 417 | } 418 | }; 419 | XHRWrapper.prototype.abort = function () { 420 | this._abort(false); 421 | }; 422 | XHRWrapper.prototype.getResponseHeader = function (name) { 423 | return this._contentType; 424 | }; 425 | XHRWrapper.prototype.setRequestHeader = function (name, value) { 426 | var xhr = this._xhr; 427 | if ("setRequestHeader" in xhr) { 428 | xhr.setRequestHeader(name, value); 429 | } 430 | }; 431 | XHRWrapper.prototype.getAllResponseHeaders = function () { 432 | // XMLHttpRequest#getAllResponseHeaders returns null for CORS requests in Firefox 3.6.28 433 | return this._xhr.getAllResponseHeaders != undefined ? this._xhr.getAllResponseHeaders() || "" : ""; 434 | }; 435 | XHRWrapper.prototype.send = function () { 436 | // loading indicator in Safari < ? (6), Chrome < 14, Firefox 437 | // https://bugzilla.mozilla.org/show_bug.cgi?id=736723 438 | if ((!("ontimeout" in XMLHttpRequest.prototype) || (!("sendAsBinary" in XMLHttpRequest.prototype) && !("mozAnon" in XMLHttpRequest.prototype))) && 439 | document != undefined && 440 | document.readyState != undefined && 441 | document.readyState !== "complete") { 442 | var that = this; 443 | that._sendTimeout = setTimeout(function () { 444 | that._sendTimeout = 0; 445 | that.send(); 446 | }, 4); 447 | return; 448 | } 449 | 450 | var xhr = this._xhr; 451 | // withCredentials should be set after "open" for Safari and Chrome (< 19 ?) 452 | if ("withCredentials" in xhr) { 453 | xhr.withCredentials = this.withCredentials; 454 | } 455 | try { 456 | // xhr.send(); throws "Not enough arguments" in Firefox 3.0 457 | xhr.send(undefined); 458 | } catch (error1) { 459 | // Safari 5.1.7, Opera 12 460 | throw error1; 461 | } 462 | }; 463 | 464 | function toLowerCase(name) { 465 | return name.replace(/[A-Z]/g, function (c) { 466 | return String.fromCharCode(c.charCodeAt(0) + 0x20); 467 | }); 468 | } 469 | 470 | function HeadersPolyfill(all) { 471 | // Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example 472 | var map = Object.create(null); 473 | var array = all.split("\r\n"); 474 | for (var i = 0; i < array.length; i += 1) { 475 | var line = array[i]; 476 | var parts = line.split(": "); 477 | var name = parts.shift(); 478 | var value = parts.join(": "); 479 | map[toLowerCase(name)] = value; 480 | } 481 | this._map = map; 482 | } 483 | HeadersPolyfill.prototype.get = function (name) { 484 | return this._map[toLowerCase(name)]; 485 | }; 486 | 487 | if (XMLHttpRequest != null && XMLHttpRequest.HEADERS_RECEIVED == null) { // IE < 9, Firefox 3.6 488 | XMLHttpRequest.HEADERS_RECEIVED = 2; 489 | } 490 | 491 | function XHRTransport() { 492 | } 493 | 494 | XHRTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 495 | xhr.open("GET", url); 496 | var offset = 0; 497 | xhr.onprogress = function () { 498 | var responseText = xhr.responseText; 499 | var chunk = responseText.slice(offset); 500 | offset += chunk.length; 501 | onProgressCallback(chunk); 502 | }; 503 | xhr.onerror = function (event) { 504 | event.preventDefault(); 505 | onFinishCallback(new Error("NetworkError")); 506 | }; 507 | xhr.onload = function () { 508 | onFinishCallback(null); 509 | }; 510 | xhr.onabort = function () { 511 | onFinishCallback(null); 512 | }; 513 | xhr.onreadystatechange = function () { 514 | if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) { 515 | var status = xhr.status; 516 | var statusText = xhr.statusText; 517 | var contentType = xhr.getResponseHeader("Content-Type"); 518 | var headers = xhr.getAllResponseHeaders(); 519 | onStartCallback(status, statusText, contentType, new HeadersPolyfill(headers)); 520 | } 521 | }; 522 | xhr.withCredentials = withCredentials; 523 | for (var name in headers) { 524 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 525 | xhr.setRequestHeader(name, headers[name]); 526 | } 527 | } 528 | xhr.send(); 529 | return xhr; 530 | }; 531 | 532 | function HeadersWrapper(headers) { 533 | this._headers = headers; 534 | } 535 | HeadersWrapper.prototype.get = function (name) { 536 | return this._headers.get(name); 537 | }; 538 | 539 | function FetchTransport() { 540 | } 541 | 542 | FetchTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 543 | var reader = null; 544 | var controller = new AbortController(); 545 | var signal = controller.signal; 546 | var textDecoder = new TextDecoder(); 547 | fetch(url, { 548 | headers: headers, 549 | credentials: withCredentials ? "include" : "same-origin", 550 | signal: signal, 551 | cache: "no-store" 552 | }).then(function (response) { 553 | reader = response.body.getReader(); 554 | onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"), new HeadersWrapper(response.headers)); 555 | // see https://github.com/promises-aplus/promises-spec/issues/179 556 | return new Promise(function (resolve, reject) { 557 | var readNextChunk = function () { 558 | reader.read().then(function (result) { 559 | if (result.done) { 560 | //Note: bytes in textDecoder are ignored 561 | resolve(undefined); 562 | } else { 563 | var chunk = textDecoder.decode(result.value, {stream: true}); 564 | onProgressCallback(chunk); 565 | readNextChunk(); 566 | } 567 | })["catch"](function (error) { 568 | reject(error); 569 | }); 570 | }; 571 | readNextChunk(); 572 | }); 573 | })["catch"](function (error) { 574 | if (error.name === "AbortError") { 575 | return undefined; 576 | } else { 577 | return error; 578 | } 579 | }).then(function (error) { 580 | onFinishCallback(error); 581 | }); 582 | return { 583 | abort: function () { 584 | if (reader != null) { 585 | reader.cancel(); // https://bugzilla.mozilla.org/show_bug.cgi?id=1583815 586 | } 587 | controller.abort(); 588 | } 589 | }; 590 | }; 591 | 592 | function EventTarget() { 593 | this._listeners = Object.create(null); 594 | } 595 | 596 | function throwError(e) { 597 | setTimeout(function () { 598 | throw e; 599 | }, 0); 600 | } 601 | 602 | EventTarget.prototype.dispatchEvent = function (event) { 603 | event.target = this; 604 | var typeListeners = this._listeners[event.type]; 605 | if (typeListeners != undefined) { 606 | var length = typeListeners.length; 607 | for (var i = 0; i < length; i += 1) { 608 | var listener = typeListeners[i]; 609 | try { 610 | if (typeof listener.handleEvent === "function") { 611 | listener.handleEvent(event); 612 | } else { 613 | listener.call(this, event); 614 | } 615 | } catch (e) { 616 | throwError(e); 617 | } 618 | } 619 | } 620 | }; 621 | EventTarget.prototype.addEventListener = function (type, listener) { 622 | type = String(type); 623 | var listeners = this._listeners; 624 | var typeListeners = listeners[type]; 625 | if (typeListeners == undefined) { 626 | typeListeners = []; 627 | listeners[type] = typeListeners; 628 | } 629 | var found = false; 630 | for (var i = 0; i < typeListeners.length; i += 1) { 631 | if (typeListeners[i] === listener) { 632 | found = true; 633 | } 634 | } 635 | if (!found) { 636 | typeListeners.push(listener); 637 | } 638 | }; 639 | EventTarget.prototype.removeEventListener = function (type, listener) { 640 | type = String(type); 641 | var listeners = this._listeners; 642 | var typeListeners = listeners[type]; 643 | if (typeListeners != undefined) { 644 | var filtered = []; 645 | for (var i = 0; i < typeListeners.length; i += 1) { 646 | if (typeListeners[i] !== listener) { 647 | filtered.push(typeListeners[i]); 648 | } 649 | } 650 | if (filtered.length === 0) { 651 | delete listeners[type]; 652 | } else { 653 | listeners[type] = filtered; 654 | } 655 | } 656 | }; 657 | 658 | function Event(type) { 659 | this.type = type; 660 | this.target = undefined; 661 | } 662 | 663 | function MessageEvent(type, options) { 664 | Event.call(this, type); 665 | this.data = options.data; 666 | this.lastEventId = options.lastEventId; 667 | } 668 | 669 | MessageEvent.prototype = Object.create(Event.prototype); 670 | 671 | function ConnectionEvent(type, options) { 672 | Event.call(this, type); 673 | this.status = options.status; 674 | this.statusText = options.statusText; 675 | this.headers = options.headers; 676 | } 677 | 678 | ConnectionEvent.prototype = Object.create(Event.prototype); 679 | 680 | function ErrorEvent(type, options) { 681 | Event.call(this, type); 682 | this.error = options.error; 683 | } 684 | 685 | ErrorEvent.prototype = Object.create(Event.prototype); 686 | 687 | var WAITING = -1; 688 | var CONNECTING = 0; 689 | var OPEN = 1; 690 | var CLOSED = 2; 691 | 692 | var AFTER_CR = -1; 693 | var FIELD_START = 0; 694 | var FIELD = 1; 695 | var VALUE_START = 2; 696 | var VALUE = 3; 697 | 698 | var contentTypeRegExp = /^text\/event\-stream(;.*)?$/i; 699 | 700 | var MINIMUM_DURATION = 1000; 701 | var MAXIMUM_DURATION = 18000000; 702 | 703 | var parseDuration = function (value, def) { 704 | var n = value == null ? def : parseInt(value, 10); 705 | if (n !== n) { 706 | n = def; 707 | } 708 | return clampDuration(n); 709 | }; 710 | var clampDuration = function (n) { 711 | return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION); 712 | }; 713 | 714 | var fire = function (that, f, event) { 715 | try { 716 | if (typeof f === "function") { 717 | f.call(that, event); 718 | } 719 | } catch (e) { 720 | throwError(e); 721 | } 722 | }; 723 | 724 | function EventSourcePolyfill(url, options) { 725 | EventTarget.call(this); 726 | options = options || {}; 727 | 728 | this.onopen = undefined; 729 | this.onmessage = undefined; 730 | this.onerror = undefined; 731 | 732 | this.url = undefined; 733 | this.readyState = undefined; 734 | this.withCredentials = undefined; 735 | this.headers = undefined; 736 | 737 | this._close = undefined; 738 | 739 | start(this, url, options); 740 | } 741 | 742 | function getBestXHRTransport() { 743 | return (XMLHttpRequest != undefined && ("withCredentials" in XMLHttpRequest.prototype)) || XDomainRequest == undefined 744 | ? new XMLHttpRequest() 745 | : new XDomainRequest(); 746 | } 747 | 748 | var isFetchSupported = fetch != undefined && Response != undefined && "body" in Response.prototype; 749 | 750 | function start(es, url, options) { 751 | url = String(url); 752 | var withCredentials = Boolean(options.withCredentials); 753 | var lastEventIdQueryParameterName = options.lastEventIdQueryParameterName || "lastEventId"; 754 | 755 | var initialRetry = clampDuration(1000); 756 | var heartbeatTimeout = parseDuration(options.heartbeatTimeout, 45000); 757 | 758 | var lastEventId = ""; 759 | var retry = initialRetry; 760 | var wasActivity = false; 761 | var textLength = 0; 762 | var headers = options.headers || {}; 763 | var TransportOption = options.Transport; 764 | var xhr = isFetchSupported && TransportOption == undefined ? undefined : new XHRWrapper(TransportOption != undefined ? new TransportOption() : getBestXHRTransport()); 765 | var transport = TransportOption != null && typeof TransportOption !== "string" ? new TransportOption() : (xhr == undefined ? new FetchTransport() : new XHRTransport()); 766 | var abortController = undefined; 767 | var timeout = 0; 768 | var currentState = WAITING; 769 | var dataBuffer = ""; 770 | var lastEventIdBuffer = ""; 771 | var eventTypeBuffer = ""; 772 | 773 | var textBuffer = ""; 774 | var state = FIELD_START; 775 | var fieldStart = 0; 776 | var valueStart = 0; 777 | 778 | var onStart = function (status, statusText, contentType, headers) { 779 | if (currentState === CONNECTING) { 780 | if (status === 200 && contentType != undefined && contentTypeRegExp.test(contentType)) { 781 | currentState = OPEN; 782 | wasActivity = Date.now(); 783 | retry = initialRetry; 784 | es.readyState = OPEN; 785 | var event = new ConnectionEvent("open", { 786 | status: status, 787 | statusText: statusText, 788 | headers: headers 789 | }); 790 | es.dispatchEvent(event); 791 | fire(es, es.onopen, event); 792 | } else { 793 | var message = ""; 794 | if (status !== 200) { 795 | if (statusText) { 796 | statusText = statusText.replace(/\s+/g, " "); 797 | } 798 | message = "EventSource's response has a status " + status + " " + statusText + " that is not 200. Aborting the connection."; 799 | } else { 800 | message = "EventSource's response has a Content-Type specifying an unsupported type: " + (contentType == undefined ? "-" : contentType.replace(/\s+/g, " ")) + ". Aborting the connection."; 801 | } 802 | close(); 803 | var event = new ConnectionEvent("error", { 804 | status: status, 805 | statusText: statusText, 806 | headers: headers 807 | }); 808 | es.dispatchEvent(event); 809 | fire(es, es.onerror, event); 810 | console.error(message); 811 | } 812 | } 813 | }; 814 | 815 | var onProgress = function (textChunk) { 816 | if (currentState === OPEN) { 817 | var n = -1; 818 | for (var i = 0; i < textChunk.length; i += 1) { 819 | var c = textChunk.charCodeAt(i); 820 | if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) { 821 | n = i; 822 | } 823 | } 824 | var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1); 825 | textBuffer = (n === -1 ? textBuffer : "") + textChunk.slice(n + 1); 826 | if (textChunk !== "") { 827 | wasActivity = Date.now(); 828 | textLength += textChunk.length; 829 | } 830 | for (var position = 0; position < chunk.length; position += 1) { 831 | var c = chunk.charCodeAt(position); 832 | if (state === AFTER_CR && c === "\n".charCodeAt(0)) { 833 | state = FIELD_START; 834 | } else { 835 | if (state === AFTER_CR) { 836 | state = FIELD_START; 837 | } 838 | if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) { 839 | if (state !== FIELD_START) { 840 | if (state === FIELD) { 841 | valueStart = position + 1; 842 | } 843 | var field = chunk.slice(fieldStart, valueStart - 1); 844 | var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === " ".charCodeAt(0) ? 1 : 0), position); 845 | if (field === "data") { 846 | dataBuffer += "\n"; 847 | dataBuffer += value; 848 | } else if (field === "id") { 849 | lastEventIdBuffer = value; 850 | } else if (field === "event") { 851 | eventTypeBuffer = value; 852 | } else if (field === "retry") { 853 | initialRetry = parseDuration(value, initialRetry); 854 | retry = initialRetry; 855 | } else if (field === "heartbeatTimeout") { 856 | heartbeatTimeout = parseDuration(value, heartbeatTimeout); 857 | if (timeout !== 0) { 858 | clearTimeout(timeout); 859 | timeout = setTimeout(function () { 860 | onTimeout(); 861 | }, heartbeatTimeout); 862 | } 863 | } 864 | } 865 | if (state === FIELD_START) { 866 | if (dataBuffer !== "") { 867 | lastEventId = lastEventIdBuffer; 868 | if (eventTypeBuffer === "") { 869 | eventTypeBuffer = "message"; 870 | } 871 | var event = new MessageEvent(eventTypeBuffer, { 872 | data: dataBuffer.slice(1), 873 | lastEventId: lastEventIdBuffer 874 | }); 875 | es.dispatchEvent(event); 876 | if (eventTypeBuffer === "open") { 877 | fire(es, es.onopen, event); 878 | } else if (eventTypeBuffer === "message") { 879 | fire(es, es.onmessage, event); 880 | } else if (eventTypeBuffer === "error") { 881 | fire(es, es.onerror, event); 882 | } 883 | if (currentState === CLOSED) { 884 | return; 885 | } 886 | } 887 | dataBuffer = ""; 888 | eventTypeBuffer = ""; 889 | } 890 | state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START; 891 | } else { 892 | if (state === FIELD_START) { 893 | fieldStart = position; 894 | state = FIELD; 895 | } 896 | if (state === FIELD) { 897 | if (c === ":".charCodeAt(0)) { 898 | valueStart = position + 1; 899 | state = VALUE_START; 900 | } 901 | } else if (state === VALUE_START) { 902 | state = VALUE; 903 | } 904 | } 905 | } 906 | } 907 | } 908 | }; 909 | 910 | var onFinish = function (error) { 911 | if (currentState === OPEN || currentState === CONNECTING) { 912 | currentState = WAITING; 913 | if (timeout !== 0) { 914 | clearTimeout(timeout); 915 | timeout = 0; 916 | } 917 | timeout = setTimeout(function () { 918 | onTimeout(); 919 | }, retry); 920 | retry = clampDuration(Math.min(initialRetry * 16, retry * 2)); 921 | 922 | es.readyState = CONNECTING; 923 | var event = new ErrorEvent("error", {error: error}); 924 | es.dispatchEvent(event); 925 | fire(es, es.onerror, event); 926 | if (error != undefined) { 927 | console.error(error); 928 | } 929 | } 930 | }; 931 | 932 | var close = function () { 933 | currentState = CLOSED; 934 | if (abortController != undefined) { 935 | abortController.abort(); 936 | abortController = undefined; 937 | } 938 | if (timeout !== 0) { 939 | clearTimeout(timeout); 940 | timeout = 0; 941 | } 942 | es.readyState = CLOSED; 943 | }; 944 | 945 | var onTimeout = function () { 946 | timeout = 0; 947 | 948 | if (currentState !== WAITING) { 949 | if (!wasActivity && abortController != undefined) { 950 | onFinish(new Error("No activity within " + heartbeatTimeout + " milliseconds." + " " + (currentState === CONNECTING ? "No response received." : textLength + " chars received.") + " " + "Reconnecting.")); 951 | if (abortController != undefined) { 952 | abortController.abort(); 953 | abortController = undefined; 954 | } 955 | } else { 956 | var nextHeartbeat = Math.max((wasActivity || Date.now()) + heartbeatTimeout - Date.now(), 1); 957 | wasActivity = false; 958 | timeout = setTimeout(function () { 959 | onTimeout(); 960 | }, nextHeartbeat); 961 | } 962 | return; 963 | } 964 | 965 | wasActivity = false; 966 | textLength = 0; 967 | timeout = setTimeout(function () { 968 | onTimeout(); 969 | }, heartbeatTimeout); 970 | 971 | currentState = CONNECTING; 972 | dataBuffer = ""; 973 | eventTypeBuffer = ""; 974 | lastEventIdBuffer = lastEventId; 975 | textBuffer = ""; 976 | fieldStart = 0; 977 | valueStart = 0; 978 | state = FIELD_START; 979 | 980 | // https://bugzilla.mozilla.org/show_bug.cgi?id=428916 981 | // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers. 982 | var requestURL = url; 983 | if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") { 984 | if (lastEventId !== "") { 985 | requestURL += (url.indexOf("?") === -1 ? "?" : "&") + lastEventIdQueryParameterName +"=" + encodeURIComponent(lastEventId); 986 | } 987 | } 988 | var withCredentials = es.withCredentials; 989 | var requestHeaders = {}; 990 | requestHeaders["Accept"] = "text/event-stream"; 991 | var headers = es.headers; 992 | if (headers != undefined) { 993 | for (var name in headers) { 994 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 995 | requestHeaders[name] = headers[name]; 996 | } 997 | } 998 | } 999 | try { 1000 | abortController = transport.open(xhr, onStart, onProgress, onFinish, requestURL, withCredentials, requestHeaders); 1001 | } catch (error) { 1002 | close(); 1003 | throw error; 1004 | } 1005 | }; 1006 | 1007 | es.url = url; 1008 | es.readyState = CONNECTING; 1009 | es.withCredentials = withCredentials; 1010 | es.headers = headers; 1011 | es._close = close; 1012 | 1013 | onTimeout(); 1014 | } 1015 | 1016 | EventSourcePolyfill.prototype = Object.create(EventTarget.prototype); 1017 | EventSourcePolyfill.prototype.CONNECTING = CONNECTING; 1018 | EventSourcePolyfill.prototype.OPEN = OPEN; 1019 | EventSourcePolyfill.prototype.CLOSED = CLOSED; 1020 | EventSourcePolyfill.prototype.close = function () { 1021 | this._close(); 1022 | }; 1023 | 1024 | EventSourcePolyfill.CONNECTING = CONNECTING; 1025 | EventSourcePolyfill.OPEN = OPEN; 1026 | EventSourcePolyfill.CLOSED = CLOSED; 1027 | EventSourcePolyfill.prototype.withCredentials = undefined; 1028 | 1029 | var R = NativeEventSource; 1030 | if (XMLHttpRequest != undefined && (NativeEventSource == undefined || !("withCredentials" in NativeEventSource.prototype))) { 1031 | // Why replace a native EventSource ? 1032 | // https://bugzilla.mozilla.org/show_bug.cgi?id=444328 1033 | // https://bugzilla.mozilla.org/show_bug.cgi?id=831392 1034 | // https://code.google.com/p/chromium/issues/detail?id=260144 1035 | // https://code.google.com/p/chromium/issues/detail?id=225654 1036 | // ... 1037 | R = EventSourcePolyfill; 1038 | } 1039 | 1040 | (function (factory) { 1041 | { 1042 | var v = factory(exports); 1043 | if (v !== undefined) { module.exports = v; } 1044 | } 1045 | })(function (exports) { 1046 | exports.EventSourcePolyfill = EventSourcePolyfill; 1047 | exports.NativeEventSource = NativeEventSource; 1048 | exports.EventSource = R; 1049 | }); 1050 | }(typeof globalThis === 'undefined' ? (typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : commonjsGlobal) : globalThis)); 1051 | }); 1052 | 1053 | var eventsource$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign(/*#__PURE__*/Object.create(null), eventsource, { 1054 | 'default': eventsource 1055 | })); 1056 | 1057 | var formatText = function (e) { return e.data; }; 1058 | 1059 | var formatJSON = function (e) { return JSON.parse(e.data); }; 1060 | 1061 | var SSEClient = function SSEClient(config) { 1062 | this._handlers = {}; 1063 | this._listeners = {}; 1064 | this._source = null; 1065 | 1066 | if (config.format) { 1067 | if (typeof config.format === 'string') { 1068 | if (config.format === 'plain') { 1069 | this._format = formatText; 1070 | } else if (config.format === 'json') { 1071 | this._format = formatJSON; 1072 | } else { 1073 | this._format = formatText; 1074 | } 1075 | } else if (typeof config.format === 'function') { 1076 | this._format = config.format; 1077 | } else { 1078 | this._format = formatText; 1079 | } 1080 | } else { 1081 | this._format = formatText; 1082 | } 1083 | 1084 | if (config.handlers) { 1085 | for (var event in config.handlers) { 1086 | this.on(event, config.handlers[event]); 1087 | } 1088 | } 1089 | 1090 | this.url = config.url; 1091 | this.withCredentials = !!config.withCredentials; 1092 | this.polyfillOptions = config.polyfillOptions || {}; 1093 | this.forcePolyfill = !!config.forcePolyfill; 1094 | }; 1095 | 1096 | var prototypeAccessors = { source: { configurable: true } }; 1097 | 1098 | prototypeAccessors.source.get = function () { 1099 | return this._source; 1100 | }; 1101 | 1102 | SSEClient.prototype.connect = function connect () { 1103 | var this$1 = this; 1104 | 1105 | if (this.forcePolyfill) { 1106 | this._source = new eventsource.EventSourcePolyfill( 1107 | this.url, 1108 | Object.assign({}, this.polyfillOptions, { 1109 | withCredentials: this.withCredentials, 1110 | }) 1111 | ); 1112 | } else { 1113 | this._source = new window.EventSource(this.url, { 1114 | withCredentials: this.withCredentials, 1115 | }); 1116 | } 1117 | 1118 | return new Promise(function (resolve, reject) { 1119 | this$1._source.onopen = function () { 1120 | // Add event listeners that were added before we connected 1121 | for (var event in this$1._listeners) { 1122 | this$1._source.addEventListener(event, this$1._listeners[event]); 1123 | } 1124 | 1125 | this$1._source.onerror = null; 1126 | 1127 | resolve(this$1); 1128 | }; 1129 | 1130 | this$1._source.onerror = reject; 1131 | }); 1132 | }; 1133 | 1134 | SSEClient.prototype.disconnect = function disconnect () { 1135 | if (this._source !== null) { 1136 | this._source.close(); 1137 | this._source = null; 1138 | } 1139 | }; 1140 | 1141 | SSEClient.prototype.on = function on (event, handler) { 1142 | if (!event) { 1143 | // Default "event-less" event 1144 | event = 'message'; 1145 | } 1146 | 1147 | if (!this._listeners[event]) { 1148 | this._create(event); 1149 | } 1150 | 1151 | this._handlers[event].push(handler); 1152 | 1153 | return this; 1154 | }; 1155 | 1156 | SSEClient.prototype.once = function once (event, handler) { 1157 | var this$1 = this; 1158 | 1159 | this.on(event, function (e) { 1160 | this$1.off(event, handler); 1161 | 1162 | handler(e); 1163 | }); 1164 | 1165 | return this; 1166 | }; 1167 | 1168 | SSEClient.prototype.off = function off (event, handler) { 1169 | if (!this._handlers[event]) { 1170 | // no handlers registered for event 1171 | return this; 1172 | } 1173 | 1174 | var idx = this._handlers[event].indexOf(handler); 1175 | if (idx === -1) { 1176 | // handler not registered for event 1177 | return this; 1178 | } 1179 | 1180 | // remove handler from event 1181 | this._handlers[event].splice(idx, 1); 1182 | 1183 | if (this._handlers[event].length === 0) { 1184 | // remove listener since no handlers exist 1185 | this._source.removeEventListener(event, this._listeners[event]); 1186 | delete this._handlers[event]; 1187 | delete this._listeners[event]; 1188 | } 1189 | 1190 | return this; 1191 | }; 1192 | 1193 | SSEClient.prototype._create = function _create (event) { 1194 | var this$1 = this; 1195 | 1196 | this._handlers[event] = []; 1197 | 1198 | this._listeners[event] = function (message) { 1199 | var data; 1200 | 1201 | try { 1202 | data = this$1._format(message); 1203 | } catch (err) { 1204 | if (typeof this$1._source.onerror === 'function') { 1205 | this$1._source.onerror(err); 1206 | } 1207 | return; 1208 | } 1209 | 1210 | this$1._handlers[event].forEach(function (handler) { return handler(data, message.lastEventId); }); 1211 | }; 1212 | 1213 | if (this._source) { 1214 | this._source.addEventListener(event, this._listeners[event]); 1215 | } 1216 | }; 1217 | 1218 | Object.defineProperties( SSEClient.prototype, prototypeAccessors ); 1219 | 1220 | function install(Vue, config) { 1221 | if (Vue.config && Vue.config.globalProperties) { 1222 | // Vue3 1223 | Vue.config.globalProperties.$sse = new SSEManager(config); 1224 | } else { 1225 | // Vue2 1226 | // eslint-disable-next-line no-param-reassign, no-multi-assign 1227 | Vue.$sse = Vue.prototype.$sse = new SSEManager(config); 1228 | } 1229 | 1230 | if (config && config.polyfill) { 1231 | Promise.resolve().then(function () { return eventsource$1; }); 1232 | } 1233 | 1234 | // This mixin allows components to specify that all clients that were 1235 | // created within it should be automatically disconnected (cleanup) 1236 | // when the component is destroyed. 1237 | Vue.mixin({ 1238 | beforeCreate: function beforeCreate() { 1239 | if (this.$options.sse && this.$options.sse.cleanup) { 1240 | // We instantiate an SSEManager for this specific instance 1241 | // in order to track it (see discussions in #13 for rationale). 1242 | this.$sse = new SSEManager(); 1243 | 1244 | // We also set $clients to an empty array, as opposed to null, 1245 | // so that beforeDestroy and create know to use it. 1246 | this.$sse.$clients = []; 1247 | } 1248 | }, 1249 | beforeDestroy: function beforeDestroy() { 1250 | if (this.$sse.$clients !== null) { 1251 | this.$sse.$clients.forEach(function (c) { return c.disconnect(); }); 1252 | this.$sse.$clients = []; 1253 | } 1254 | }, 1255 | }); 1256 | } 1257 | 1258 | var SSEManager = function SSEManager(config) { 1259 | this.$defaultConfig = Object.assign( 1260 | { 1261 | format: formatText, 1262 | sendCredentials: false, 1263 | }, 1264 | config 1265 | ); 1266 | 1267 | this.$clients = null; 1268 | }; 1269 | 1270 | SSEManager.prototype.create = function create (configOrURL) { 1271 | var config; 1272 | if (typeof configOrURL === 'object') { 1273 | config = configOrURL; 1274 | } else if (typeof configOrURL === 'string') { 1275 | config = { 1276 | url: configOrURL, 1277 | }; 1278 | } else { 1279 | config = {}; 1280 | } 1281 | 1282 | var client = new SSEClient(Object.assign({}, this.$defaultConfig, config)); 1283 | 1284 | // If $clients is not null, then it's array that we should push this 1285 | // client into for later cleanup in our mixin's beforeDestroy. 1286 | if (this.$clients !== null) { 1287 | this.$clients.push(client); 1288 | } 1289 | 1290 | return client; 1291 | }; 1292 | 1293 | var index_cjs = { 1294 | SSEManager: SSEManager, 1295 | install: install, 1296 | }; 1297 | 1298 | module.exports = index_cjs; 1299 | -------------------------------------------------------------------------------- /dist/vue-sse.esm.browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-sse v2.5.0 3 | * (c) 2021 James Churchard 4 | * @license MIT 5 | */ 6 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 7 | 8 | function createCommonjsModule(fn) { 9 | var module = { exports: {} }; 10 | return fn(module, module.exports), module.exports; 11 | } 12 | 13 | /** @license 14 | * eventsource.js 15 | * Available under MIT License (MIT) 16 | * https://github.com/Yaffle/EventSource/ 17 | */ 18 | 19 | var eventsource = createCommonjsModule(function (module, exports) { 20 | /*jslint indent: 2, vars: true, plusplus: true */ 21 | /*global setTimeout, clearTimeout */ 22 | 23 | (function (global) { 24 | 25 | var setTimeout = global.setTimeout; 26 | var clearTimeout = global.clearTimeout; 27 | var XMLHttpRequest = global.XMLHttpRequest; 28 | var XDomainRequest = global.XDomainRequest; 29 | var ActiveXObject = global.ActiveXObject; 30 | var NativeEventSource = global.EventSource; 31 | 32 | var document = global.document; 33 | var Promise = global.Promise; 34 | var fetch = global.fetch; 35 | var Response = global.Response; 36 | var TextDecoder = global.TextDecoder; 37 | var TextEncoder = global.TextEncoder; 38 | var AbortController = global.AbortController; 39 | 40 | if (typeof window !== "undefined" && !("readyState" in document) && document.body == null) { // Firefox 2 41 | document.readyState = "loading"; 42 | window.addEventListener("load", function (event) { 43 | document.readyState = "complete"; 44 | }, false); 45 | } 46 | 47 | if (XMLHttpRequest == null && ActiveXObject != null) { // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest_in_IE6 48 | XMLHttpRequest = function () { 49 | return new ActiveXObject("Microsoft.XMLHTTP"); 50 | }; 51 | } 52 | 53 | if (Object.create == undefined) { 54 | Object.create = function (C) { 55 | function F(){} 56 | F.prototype = C; 57 | return new F(); 58 | }; 59 | } 60 | 61 | if (!Date.now) { 62 | Date.now = function now() { 63 | return new Date().getTime(); 64 | }; 65 | } 66 | 67 | // see #118 (Promise#finally with polyfilled Promise) 68 | // see #123 (data URLs crash Edge) 69 | // see #125 (CSP violations) 70 | // see pull/#138 71 | // => No way to polyfill Promise#finally 72 | 73 | if (AbortController == undefined) { 74 | var originalFetch2 = fetch; 75 | fetch = function (url, options) { 76 | var signal = options.signal; 77 | return originalFetch2(url, {headers: options.headers, credentials: options.credentials, cache: options.cache}).then(function (response) { 78 | var reader = response.body.getReader(); 79 | signal._reader = reader; 80 | if (signal._aborted) { 81 | signal._reader.cancel(); 82 | } 83 | return { 84 | status: response.status, 85 | statusText: response.statusText, 86 | headers: response.headers, 87 | body: { 88 | getReader: function () { 89 | return reader; 90 | } 91 | } 92 | }; 93 | }); 94 | }; 95 | AbortController = function () { 96 | this.signal = { 97 | _reader: null, 98 | _aborted: false 99 | }; 100 | this.abort = function () { 101 | if (this.signal._reader != null) { 102 | this.signal._reader.cancel(); 103 | } 104 | this.signal._aborted = true; 105 | }; 106 | }; 107 | } 108 | 109 | function TextDecoderPolyfill() { 110 | this.bitsNeeded = 0; 111 | this.codePoint = 0; 112 | } 113 | 114 | TextDecoderPolyfill.prototype.decode = function (octets) { 115 | function valid(codePoint, shift, octetsCount) { 116 | if (octetsCount === 1) { 117 | return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07FF; 118 | } 119 | if (octetsCount === 2) { 120 | return codePoint >= 0x0800 >> shift && codePoint << shift <= 0xD7FF || codePoint >= 0xE000 >> shift && codePoint << shift <= 0xFFFF; 121 | } 122 | if (octetsCount === 3) { 123 | return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10FFFF; 124 | } 125 | throw new Error(); 126 | } 127 | function octetsCount(bitsNeeded, codePoint) { 128 | if (bitsNeeded === 6 * 1) { 129 | return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1; 130 | } 131 | if (bitsNeeded === 6 * 2) { 132 | return codePoint > 15 ? 3 : 2; 133 | } 134 | if (bitsNeeded === 6 * 3) { 135 | return 3; 136 | } 137 | throw new Error(); 138 | } 139 | var REPLACER = 0xFFFD; 140 | var string = ""; 141 | var bitsNeeded = this.bitsNeeded; 142 | var codePoint = this.codePoint; 143 | for (var i = 0; i < octets.length; i += 1) { 144 | var octet = octets[i]; 145 | if (bitsNeeded !== 0) { 146 | if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) { 147 | bitsNeeded = 0; 148 | codePoint = REPLACER; 149 | string += String.fromCharCode(codePoint); 150 | } 151 | } 152 | if (bitsNeeded === 0) { 153 | if (octet >= 0 && octet <= 127) { 154 | bitsNeeded = 0; 155 | codePoint = octet; 156 | } else if (octet >= 192 && octet <= 223) { 157 | bitsNeeded = 6 * 1; 158 | codePoint = octet & 31; 159 | } else if (octet >= 224 && octet <= 239) { 160 | bitsNeeded = 6 * 2; 161 | codePoint = octet & 15; 162 | } else if (octet >= 240 && octet <= 247) { 163 | bitsNeeded = 6 * 3; 164 | codePoint = octet & 7; 165 | } else { 166 | bitsNeeded = 0; 167 | codePoint = REPLACER; 168 | } 169 | if (bitsNeeded !== 0 && !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint))) { 170 | bitsNeeded = 0; 171 | codePoint = REPLACER; 172 | } 173 | } else { 174 | bitsNeeded -= 6; 175 | codePoint = codePoint << 6 | octet & 63; 176 | } 177 | if (bitsNeeded === 0) { 178 | if (codePoint <= 0xFFFF) { 179 | string += String.fromCharCode(codePoint); 180 | } else { 181 | string += String.fromCharCode(0xD800 + (codePoint - 0xFFFF - 1 >> 10)); 182 | string += String.fromCharCode(0xDC00 + (codePoint - 0xFFFF - 1 & 0x3FF)); 183 | } 184 | } 185 | } 186 | this.bitsNeeded = bitsNeeded; 187 | this.codePoint = codePoint; 188 | return string; 189 | }; 190 | 191 | // Firefox < 38 throws an error with stream option 192 | var supportsStreamOption = function () { 193 | try { 194 | return new TextDecoder().decode(new TextEncoder().encode("test"), {stream: true}) === "test"; 195 | } catch (error) { 196 | console.debug("TextDecoder does not support streaming option. Using polyfill instead: " + error); 197 | } 198 | return false; 199 | }; 200 | 201 | // IE, Edge 202 | if (TextDecoder == undefined || TextEncoder == undefined || !supportsStreamOption()) { 203 | TextDecoder = TextDecoderPolyfill; 204 | } 205 | 206 | var k = function () { 207 | }; 208 | 209 | function XHRWrapper(xhr) { 210 | this.withCredentials = false; 211 | this.readyState = 0; 212 | this.status = 0; 213 | this.statusText = ""; 214 | this.responseText = ""; 215 | this.onprogress = k; 216 | this.onload = k; 217 | this.onerror = k; 218 | this.onreadystatechange = k; 219 | this._contentType = ""; 220 | this._xhr = xhr; 221 | this._sendTimeout = 0; 222 | this._abort = k; 223 | } 224 | 225 | XHRWrapper.prototype.open = function (method, url) { 226 | this._abort(true); 227 | 228 | var that = this; 229 | var xhr = this._xhr; 230 | var state = 1; 231 | var timeout = 0; 232 | 233 | this._abort = function (silent) { 234 | if (that._sendTimeout !== 0) { 235 | clearTimeout(that._sendTimeout); 236 | that._sendTimeout = 0; 237 | } 238 | if (state === 1 || state === 2 || state === 3) { 239 | state = 4; 240 | xhr.onload = k; 241 | xhr.onerror = k; 242 | xhr.onabort = k; 243 | xhr.onprogress = k; 244 | xhr.onreadystatechange = k; 245 | // IE 8 - 9: XDomainRequest#abort() does not fire any event 246 | // Opera < 10: XMLHttpRequest#abort() does not fire any event 247 | xhr.abort(); 248 | if (timeout !== 0) { 249 | clearTimeout(timeout); 250 | timeout = 0; 251 | } 252 | if (!silent) { 253 | that.readyState = 4; 254 | that.onabort(null); 255 | that.onreadystatechange(); 256 | } 257 | } 258 | state = 0; 259 | }; 260 | 261 | var onStart = function () { 262 | if (state === 1) { 263 | //state = 2; 264 | var status = 0; 265 | var statusText = ""; 266 | var contentType = undefined; 267 | if (!("contentType" in xhr)) { 268 | try { 269 | status = xhr.status; 270 | statusText = xhr.statusText; 271 | contentType = xhr.getResponseHeader("Content-Type"); 272 | } catch (error) { 273 | // IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3 274 | // Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2 275 | // https://bugs.webkit.org/show_bug.cgi?id=29121 276 | status = 0; 277 | statusText = ""; 278 | contentType = undefined; 279 | // Firefox < 14, Chrome ?, Safari ? 280 | // https://bugs.webkit.org/show_bug.cgi?id=29658 281 | // https://bugs.webkit.org/show_bug.cgi?id=77854 282 | } 283 | } else { 284 | status = 200; 285 | statusText = "OK"; 286 | contentType = xhr.contentType; 287 | } 288 | if (status !== 0) { 289 | state = 2; 290 | that.readyState = 2; 291 | that.status = status; 292 | that.statusText = statusText; 293 | that._contentType = contentType; 294 | that.onreadystatechange(); 295 | } 296 | } 297 | }; 298 | var onProgress = function () { 299 | onStart(); 300 | if (state === 2 || state === 3) { 301 | state = 3; 302 | var responseText = ""; 303 | try { 304 | responseText = xhr.responseText; 305 | } catch (error) { 306 | // IE 8 - 9 with XMLHttpRequest 307 | } 308 | that.readyState = 3; 309 | that.responseText = responseText; 310 | that.onprogress(); 311 | } 312 | }; 313 | var onFinish = function (type, event) { 314 | if (event == null || event.preventDefault == null) { 315 | event = { 316 | preventDefault: k 317 | }; 318 | } 319 | // Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3) 320 | // IE 8 fires "onload" without "onprogress" 321 | onProgress(); 322 | if (state === 1 || state === 2 || state === 3) { 323 | state = 4; 324 | if (timeout !== 0) { 325 | clearTimeout(timeout); 326 | timeout = 0; 327 | } 328 | that.readyState = 4; 329 | if (type === "load") { 330 | that.onload(event); 331 | } else if (type === "error") { 332 | that.onerror(event); 333 | } else if (type === "abort") { 334 | that.onabort(event); 335 | } else { 336 | throw new TypeError(); 337 | } 338 | that.onreadystatechange(); 339 | } 340 | }; 341 | var onReadyStateChange = function (event) { 342 | if (xhr != undefined) { // Opera 12 343 | if (xhr.readyState === 4) { 344 | if (!("onload" in xhr) || !("onerror" in xhr) || !("onabort" in xhr)) { 345 | onFinish(xhr.responseText === "" ? "error" : "load", event); 346 | } 347 | } else if (xhr.readyState === 3) { 348 | if (!("onprogress" in xhr)) { // testing XMLHttpRequest#responseText too many times is too slow in IE 11 349 | // and in Firefox 3.6 350 | onProgress(); 351 | } 352 | } else if (xhr.readyState === 2) { 353 | onStart(); 354 | } 355 | } 356 | }; 357 | var onTimeout = function () { 358 | timeout = setTimeout(function () { 359 | onTimeout(); 360 | }, 500); 361 | if (xhr.readyState === 3) { 362 | onProgress(); 363 | } 364 | }; 365 | 366 | // XDomainRequest#abort removes onprogress, onerror, onload 367 | if ("onload" in xhr) { 368 | xhr.onload = function (event) { 369 | onFinish("load", event); 370 | }; 371 | } 372 | if ("onerror" in xhr) { 373 | xhr.onerror = function (event) { 374 | onFinish("error", event); 375 | }; 376 | } 377 | // improper fix to match Firefox behaviour, but it is better than just ignore abort 378 | // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596 379 | // https://bugzilla.mozilla.org/show_bug.cgi?id=880200 380 | // https://code.google.com/p/chromium/issues/detail?id=153570 381 | // IE 8 fires "onload" without "onprogress 382 | if ("onabort" in xhr) { 383 | xhr.onabort = function (event) { 384 | onFinish("abort", event); 385 | }; 386 | } 387 | 388 | if ("onprogress" in xhr) { 389 | xhr.onprogress = onProgress; 390 | } 391 | 392 | // IE 8 - 9 (XMLHTTPRequest) 393 | // Opera < 12 394 | // Firefox < 3.5 395 | // Firefox 3.5 - 3.6 - ? < 9.0 396 | // onprogress is not fired sometimes or delayed 397 | // see also #64 (significant lag in IE 11) 398 | if ("onreadystatechange" in xhr) { 399 | xhr.onreadystatechange = function (event) { 400 | onReadyStateChange(event); 401 | }; 402 | } 403 | 404 | if ("contentType" in xhr || !("ontimeout" in XMLHttpRequest.prototype)) { 405 | url += (url.indexOf("?") === -1 ? "?" : "&") + "padding=true"; 406 | } 407 | xhr.open(method, url, true); 408 | 409 | if ("readyState" in xhr) { 410 | // workaround for Opera 12 issue with "progress" events 411 | // #91 (XMLHttpRequest onprogress not fired for streaming response in Edge 14-15-?) 412 | timeout = setTimeout(function () { 413 | onTimeout(); 414 | }, 0); 415 | } 416 | }; 417 | XHRWrapper.prototype.abort = function () { 418 | this._abort(false); 419 | }; 420 | XHRWrapper.prototype.getResponseHeader = function (name) { 421 | return this._contentType; 422 | }; 423 | XHRWrapper.prototype.setRequestHeader = function (name, value) { 424 | var xhr = this._xhr; 425 | if ("setRequestHeader" in xhr) { 426 | xhr.setRequestHeader(name, value); 427 | } 428 | }; 429 | XHRWrapper.prototype.getAllResponseHeaders = function () { 430 | // XMLHttpRequest#getAllResponseHeaders returns null for CORS requests in Firefox 3.6.28 431 | return this._xhr.getAllResponseHeaders != undefined ? this._xhr.getAllResponseHeaders() || "" : ""; 432 | }; 433 | XHRWrapper.prototype.send = function () { 434 | // loading indicator in Safari < ? (6), Chrome < 14, Firefox 435 | // https://bugzilla.mozilla.org/show_bug.cgi?id=736723 436 | if ((!("ontimeout" in XMLHttpRequest.prototype) || (!("sendAsBinary" in XMLHttpRequest.prototype) && !("mozAnon" in XMLHttpRequest.prototype))) && 437 | document != undefined && 438 | document.readyState != undefined && 439 | document.readyState !== "complete") { 440 | var that = this; 441 | that._sendTimeout = setTimeout(function () { 442 | that._sendTimeout = 0; 443 | that.send(); 444 | }, 4); 445 | return; 446 | } 447 | 448 | var xhr = this._xhr; 449 | // withCredentials should be set after "open" for Safari and Chrome (< 19 ?) 450 | if ("withCredentials" in xhr) { 451 | xhr.withCredentials = this.withCredentials; 452 | } 453 | try { 454 | // xhr.send(); throws "Not enough arguments" in Firefox 3.0 455 | xhr.send(undefined); 456 | } catch (error1) { 457 | // Safari 5.1.7, Opera 12 458 | throw error1; 459 | } 460 | }; 461 | 462 | function toLowerCase(name) { 463 | return name.replace(/[A-Z]/g, function (c) { 464 | return String.fromCharCode(c.charCodeAt(0) + 0x20); 465 | }); 466 | } 467 | 468 | function HeadersPolyfill(all) { 469 | // Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example 470 | var map = Object.create(null); 471 | var array = all.split("\r\n"); 472 | for (var i = 0; i < array.length; i += 1) { 473 | var line = array[i]; 474 | var parts = line.split(": "); 475 | var name = parts.shift(); 476 | var value = parts.join(": "); 477 | map[toLowerCase(name)] = value; 478 | } 479 | this._map = map; 480 | } 481 | HeadersPolyfill.prototype.get = function (name) { 482 | return this._map[toLowerCase(name)]; 483 | }; 484 | 485 | if (XMLHttpRequest != null && XMLHttpRequest.HEADERS_RECEIVED == null) { // IE < 9, Firefox 3.6 486 | XMLHttpRequest.HEADERS_RECEIVED = 2; 487 | } 488 | 489 | function XHRTransport() { 490 | } 491 | 492 | XHRTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 493 | xhr.open("GET", url); 494 | var offset = 0; 495 | xhr.onprogress = function () { 496 | var responseText = xhr.responseText; 497 | var chunk = responseText.slice(offset); 498 | offset += chunk.length; 499 | onProgressCallback(chunk); 500 | }; 501 | xhr.onerror = function (event) { 502 | event.preventDefault(); 503 | onFinishCallback(new Error("NetworkError")); 504 | }; 505 | xhr.onload = function () { 506 | onFinishCallback(null); 507 | }; 508 | xhr.onabort = function () { 509 | onFinishCallback(null); 510 | }; 511 | xhr.onreadystatechange = function () { 512 | if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) { 513 | var status = xhr.status; 514 | var statusText = xhr.statusText; 515 | var contentType = xhr.getResponseHeader("Content-Type"); 516 | var headers = xhr.getAllResponseHeaders(); 517 | onStartCallback(status, statusText, contentType, new HeadersPolyfill(headers)); 518 | } 519 | }; 520 | xhr.withCredentials = withCredentials; 521 | for (var name in headers) { 522 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 523 | xhr.setRequestHeader(name, headers[name]); 524 | } 525 | } 526 | xhr.send(); 527 | return xhr; 528 | }; 529 | 530 | function HeadersWrapper(headers) { 531 | this._headers = headers; 532 | } 533 | HeadersWrapper.prototype.get = function (name) { 534 | return this._headers.get(name); 535 | }; 536 | 537 | function FetchTransport() { 538 | } 539 | 540 | FetchTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 541 | var reader = null; 542 | var controller = new AbortController(); 543 | var signal = controller.signal; 544 | var textDecoder = new TextDecoder(); 545 | fetch(url, { 546 | headers: headers, 547 | credentials: withCredentials ? "include" : "same-origin", 548 | signal: signal, 549 | cache: "no-store" 550 | }).then(function (response) { 551 | reader = response.body.getReader(); 552 | onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"), new HeadersWrapper(response.headers)); 553 | // see https://github.com/promises-aplus/promises-spec/issues/179 554 | return new Promise(function (resolve, reject) { 555 | var readNextChunk = function () { 556 | reader.read().then(function (result) { 557 | if (result.done) { 558 | //Note: bytes in textDecoder are ignored 559 | resolve(undefined); 560 | } else { 561 | var chunk = textDecoder.decode(result.value, {stream: true}); 562 | onProgressCallback(chunk); 563 | readNextChunk(); 564 | } 565 | })["catch"](function (error) { 566 | reject(error); 567 | }); 568 | }; 569 | readNextChunk(); 570 | }); 571 | })["catch"](function (error) { 572 | if (error.name === "AbortError") { 573 | return undefined; 574 | } else { 575 | return error; 576 | } 577 | }).then(function (error) { 578 | onFinishCallback(error); 579 | }); 580 | return { 581 | abort: function () { 582 | if (reader != null) { 583 | reader.cancel(); // https://bugzilla.mozilla.org/show_bug.cgi?id=1583815 584 | } 585 | controller.abort(); 586 | } 587 | }; 588 | }; 589 | 590 | function EventTarget() { 591 | this._listeners = Object.create(null); 592 | } 593 | 594 | function throwError(e) { 595 | setTimeout(function () { 596 | throw e; 597 | }, 0); 598 | } 599 | 600 | EventTarget.prototype.dispatchEvent = function (event) { 601 | event.target = this; 602 | var typeListeners = this._listeners[event.type]; 603 | if (typeListeners != undefined) { 604 | var length = typeListeners.length; 605 | for (var i = 0; i < length; i += 1) { 606 | var listener = typeListeners[i]; 607 | try { 608 | if (typeof listener.handleEvent === "function") { 609 | listener.handleEvent(event); 610 | } else { 611 | listener.call(this, event); 612 | } 613 | } catch (e) { 614 | throwError(e); 615 | } 616 | } 617 | } 618 | }; 619 | EventTarget.prototype.addEventListener = function (type, listener) { 620 | type = String(type); 621 | var listeners = this._listeners; 622 | var typeListeners = listeners[type]; 623 | if (typeListeners == undefined) { 624 | typeListeners = []; 625 | listeners[type] = typeListeners; 626 | } 627 | var found = false; 628 | for (var i = 0; i < typeListeners.length; i += 1) { 629 | if (typeListeners[i] === listener) { 630 | found = true; 631 | } 632 | } 633 | if (!found) { 634 | typeListeners.push(listener); 635 | } 636 | }; 637 | EventTarget.prototype.removeEventListener = function (type, listener) { 638 | type = String(type); 639 | var listeners = this._listeners; 640 | var typeListeners = listeners[type]; 641 | if (typeListeners != undefined) { 642 | var filtered = []; 643 | for (var i = 0; i < typeListeners.length; i += 1) { 644 | if (typeListeners[i] !== listener) { 645 | filtered.push(typeListeners[i]); 646 | } 647 | } 648 | if (filtered.length === 0) { 649 | delete listeners[type]; 650 | } else { 651 | listeners[type] = filtered; 652 | } 653 | } 654 | }; 655 | 656 | function Event(type) { 657 | this.type = type; 658 | this.target = undefined; 659 | } 660 | 661 | function MessageEvent(type, options) { 662 | Event.call(this, type); 663 | this.data = options.data; 664 | this.lastEventId = options.lastEventId; 665 | } 666 | 667 | MessageEvent.prototype = Object.create(Event.prototype); 668 | 669 | function ConnectionEvent(type, options) { 670 | Event.call(this, type); 671 | this.status = options.status; 672 | this.statusText = options.statusText; 673 | this.headers = options.headers; 674 | } 675 | 676 | ConnectionEvent.prototype = Object.create(Event.prototype); 677 | 678 | function ErrorEvent(type, options) { 679 | Event.call(this, type); 680 | this.error = options.error; 681 | } 682 | 683 | ErrorEvent.prototype = Object.create(Event.prototype); 684 | 685 | var WAITING = -1; 686 | var CONNECTING = 0; 687 | var OPEN = 1; 688 | var CLOSED = 2; 689 | 690 | var AFTER_CR = -1; 691 | var FIELD_START = 0; 692 | var FIELD = 1; 693 | var VALUE_START = 2; 694 | var VALUE = 3; 695 | 696 | var contentTypeRegExp = /^text\/event\-stream(;.*)?$/i; 697 | 698 | var MINIMUM_DURATION = 1000; 699 | var MAXIMUM_DURATION = 18000000; 700 | 701 | var parseDuration = function (value, def) { 702 | var n = value == null ? def : parseInt(value, 10); 703 | if (n !== n) { 704 | n = def; 705 | } 706 | return clampDuration(n); 707 | }; 708 | var clampDuration = function (n) { 709 | return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION); 710 | }; 711 | 712 | var fire = function (that, f, event) { 713 | try { 714 | if (typeof f === "function") { 715 | f.call(that, event); 716 | } 717 | } catch (e) { 718 | throwError(e); 719 | } 720 | }; 721 | 722 | function EventSourcePolyfill(url, options) { 723 | EventTarget.call(this); 724 | options = options || {}; 725 | 726 | this.onopen = undefined; 727 | this.onmessage = undefined; 728 | this.onerror = undefined; 729 | 730 | this.url = undefined; 731 | this.readyState = undefined; 732 | this.withCredentials = undefined; 733 | this.headers = undefined; 734 | 735 | this._close = undefined; 736 | 737 | start(this, url, options); 738 | } 739 | 740 | function getBestXHRTransport() { 741 | return (XMLHttpRequest != undefined && ("withCredentials" in XMLHttpRequest.prototype)) || XDomainRequest == undefined 742 | ? new XMLHttpRequest() 743 | : new XDomainRequest(); 744 | } 745 | 746 | var isFetchSupported = fetch != undefined && Response != undefined && "body" in Response.prototype; 747 | 748 | function start(es, url, options) { 749 | url = String(url); 750 | var withCredentials = Boolean(options.withCredentials); 751 | var lastEventIdQueryParameterName = options.lastEventIdQueryParameterName || "lastEventId"; 752 | 753 | var initialRetry = clampDuration(1000); 754 | var heartbeatTimeout = parseDuration(options.heartbeatTimeout, 45000); 755 | 756 | var lastEventId = ""; 757 | var retry = initialRetry; 758 | var wasActivity = false; 759 | var textLength = 0; 760 | var headers = options.headers || {}; 761 | var TransportOption = options.Transport; 762 | var xhr = isFetchSupported && TransportOption == undefined ? undefined : new XHRWrapper(TransportOption != undefined ? new TransportOption() : getBestXHRTransport()); 763 | var transport = TransportOption != null && typeof TransportOption !== "string" ? new TransportOption() : (xhr == undefined ? new FetchTransport() : new XHRTransport()); 764 | var abortController = undefined; 765 | var timeout = 0; 766 | var currentState = WAITING; 767 | var dataBuffer = ""; 768 | var lastEventIdBuffer = ""; 769 | var eventTypeBuffer = ""; 770 | 771 | var textBuffer = ""; 772 | var state = FIELD_START; 773 | var fieldStart = 0; 774 | var valueStart = 0; 775 | 776 | var onStart = function (status, statusText, contentType, headers) { 777 | if (currentState === CONNECTING) { 778 | if (status === 200 && contentType != undefined && contentTypeRegExp.test(contentType)) { 779 | currentState = OPEN; 780 | wasActivity = Date.now(); 781 | retry = initialRetry; 782 | es.readyState = OPEN; 783 | var event = new ConnectionEvent("open", { 784 | status: status, 785 | statusText: statusText, 786 | headers: headers 787 | }); 788 | es.dispatchEvent(event); 789 | fire(es, es.onopen, event); 790 | } else { 791 | var message = ""; 792 | if (status !== 200) { 793 | if (statusText) { 794 | statusText = statusText.replace(/\s+/g, " "); 795 | } 796 | message = "EventSource's response has a status " + status + " " + statusText + " that is not 200. Aborting the connection."; 797 | } else { 798 | message = "EventSource's response has a Content-Type specifying an unsupported type: " + (contentType == undefined ? "-" : contentType.replace(/\s+/g, " ")) + ". Aborting the connection."; 799 | } 800 | close(); 801 | var event = new ConnectionEvent("error", { 802 | status: status, 803 | statusText: statusText, 804 | headers: headers 805 | }); 806 | es.dispatchEvent(event); 807 | fire(es, es.onerror, event); 808 | console.error(message); 809 | } 810 | } 811 | }; 812 | 813 | var onProgress = function (textChunk) { 814 | if (currentState === OPEN) { 815 | var n = -1; 816 | for (var i = 0; i < textChunk.length; i += 1) { 817 | var c = textChunk.charCodeAt(i); 818 | if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) { 819 | n = i; 820 | } 821 | } 822 | var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1); 823 | textBuffer = (n === -1 ? textBuffer : "") + textChunk.slice(n + 1); 824 | if (textChunk !== "") { 825 | wasActivity = Date.now(); 826 | textLength += textChunk.length; 827 | } 828 | for (var position = 0; position < chunk.length; position += 1) { 829 | var c = chunk.charCodeAt(position); 830 | if (state === AFTER_CR && c === "\n".charCodeAt(0)) { 831 | state = FIELD_START; 832 | } else { 833 | if (state === AFTER_CR) { 834 | state = FIELD_START; 835 | } 836 | if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) { 837 | if (state !== FIELD_START) { 838 | if (state === FIELD) { 839 | valueStart = position + 1; 840 | } 841 | var field = chunk.slice(fieldStart, valueStart - 1); 842 | var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === " ".charCodeAt(0) ? 1 : 0), position); 843 | if (field === "data") { 844 | dataBuffer += "\n"; 845 | dataBuffer += value; 846 | } else if (field === "id") { 847 | lastEventIdBuffer = value; 848 | } else if (field === "event") { 849 | eventTypeBuffer = value; 850 | } else if (field === "retry") { 851 | initialRetry = parseDuration(value, initialRetry); 852 | retry = initialRetry; 853 | } else if (field === "heartbeatTimeout") { 854 | heartbeatTimeout = parseDuration(value, heartbeatTimeout); 855 | if (timeout !== 0) { 856 | clearTimeout(timeout); 857 | timeout = setTimeout(function () { 858 | onTimeout(); 859 | }, heartbeatTimeout); 860 | } 861 | } 862 | } 863 | if (state === FIELD_START) { 864 | if (dataBuffer !== "") { 865 | lastEventId = lastEventIdBuffer; 866 | if (eventTypeBuffer === "") { 867 | eventTypeBuffer = "message"; 868 | } 869 | var event = new MessageEvent(eventTypeBuffer, { 870 | data: dataBuffer.slice(1), 871 | lastEventId: lastEventIdBuffer 872 | }); 873 | es.dispatchEvent(event); 874 | if (eventTypeBuffer === "open") { 875 | fire(es, es.onopen, event); 876 | } else if (eventTypeBuffer === "message") { 877 | fire(es, es.onmessage, event); 878 | } else if (eventTypeBuffer === "error") { 879 | fire(es, es.onerror, event); 880 | } 881 | if (currentState === CLOSED) { 882 | return; 883 | } 884 | } 885 | dataBuffer = ""; 886 | eventTypeBuffer = ""; 887 | } 888 | state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START; 889 | } else { 890 | if (state === FIELD_START) { 891 | fieldStart = position; 892 | state = FIELD; 893 | } 894 | if (state === FIELD) { 895 | if (c === ":".charCodeAt(0)) { 896 | valueStart = position + 1; 897 | state = VALUE_START; 898 | } 899 | } else if (state === VALUE_START) { 900 | state = VALUE; 901 | } 902 | } 903 | } 904 | } 905 | } 906 | }; 907 | 908 | var onFinish = function (error) { 909 | if (currentState === OPEN || currentState === CONNECTING) { 910 | currentState = WAITING; 911 | if (timeout !== 0) { 912 | clearTimeout(timeout); 913 | timeout = 0; 914 | } 915 | timeout = setTimeout(function () { 916 | onTimeout(); 917 | }, retry); 918 | retry = clampDuration(Math.min(initialRetry * 16, retry * 2)); 919 | 920 | es.readyState = CONNECTING; 921 | var event = new ErrorEvent("error", {error: error}); 922 | es.dispatchEvent(event); 923 | fire(es, es.onerror, event); 924 | if (error != undefined) { 925 | console.error(error); 926 | } 927 | } 928 | }; 929 | 930 | var close = function () { 931 | currentState = CLOSED; 932 | if (abortController != undefined) { 933 | abortController.abort(); 934 | abortController = undefined; 935 | } 936 | if (timeout !== 0) { 937 | clearTimeout(timeout); 938 | timeout = 0; 939 | } 940 | es.readyState = CLOSED; 941 | }; 942 | 943 | var onTimeout = function () { 944 | timeout = 0; 945 | 946 | if (currentState !== WAITING) { 947 | if (!wasActivity && abortController != undefined) { 948 | onFinish(new Error("No activity within " + heartbeatTimeout + " milliseconds." + " " + (currentState === CONNECTING ? "No response received." : textLength + " chars received.") + " " + "Reconnecting.")); 949 | if (abortController != undefined) { 950 | abortController.abort(); 951 | abortController = undefined; 952 | } 953 | } else { 954 | var nextHeartbeat = Math.max((wasActivity || Date.now()) + heartbeatTimeout - Date.now(), 1); 955 | wasActivity = false; 956 | timeout = setTimeout(function () { 957 | onTimeout(); 958 | }, nextHeartbeat); 959 | } 960 | return; 961 | } 962 | 963 | wasActivity = false; 964 | textLength = 0; 965 | timeout = setTimeout(function () { 966 | onTimeout(); 967 | }, heartbeatTimeout); 968 | 969 | currentState = CONNECTING; 970 | dataBuffer = ""; 971 | eventTypeBuffer = ""; 972 | lastEventIdBuffer = lastEventId; 973 | textBuffer = ""; 974 | fieldStart = 0; 975 | valueStart = 0; 976 | state = FIELD_START; 977 | 978 | // https://bugzilla.mozilla.org/show_bug.cgi?id=428916 979 | // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers. 980 | var requestURL = url; 981 | if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") { 982 | if (lastEventId !== "") { 983 | requestURL += (url.indexOf("?") === -1 ? "?" : "&") + lastEventIdQueryParameterName +"=" + encodeURIComponent(lastEventId); 984 | } 985 | } 986 | var withCredentials = es.withCredentials; 987 | var requestHeaders = {}; 988 | requestHeaders["Accept"] = "text/event-stream"; 989 | var headers = es.headers; 990 | if (headers != undefined) { 991 | for (var name in headers) { 992 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 993 | requestHeaders[name] = headers[name]; 994 | } 995 | } 996 | } 997 | try { 998 | abortController = transport.open(xhr, onStart, onProgress, onFinish, requestURL, withCredentials, requestHeaders); 999 | } catch (error) { 1000 | close(); 1001 | throw error; 1002 | } 1003 | }; 1004 | 1005 | es.url = url; 1006 | es.readyState = CONNECTING; 1007 | es.withCredentials = withCredentials; 1008 | es.headers = headers; 1009 | es._close = close; 1010 | 1011 | onTimeout(); 1012 | } 1013 | 1014 | EventSourcePolyfill.prototype = Object.create(EventTarget.prototype); 1015 | EventSourcePolyfill.prototype.CONNECTING = CONNECTING; 1016 | EventSourcePolyfill.prototype.OPEN = OPEN; 1017 | EventSourcePolyfill.prototype.CLOSED = CLOSED; 1018 | EventSourcePolyfill.prototype.close = function () { 1019 | this._close(); 1020 | }; 1021 | 1022 | EventSourcePolyfill.CONNECTING = CONNECTING; 1023 | EventSourcePolyfill.OPEN = OPEN; 1024 | EventSourcePolyfill.CLOSED = CLOSED; 1025 | EventSourcePolyfill.prototype.withCredentials = undefined; 1026 | 1027 | var R = NativeEventSource; 1028 | if (XMLHttpRequest != undefined && (NativeEventSource == undefined || !("withCredentials" in NativeEventSource.prototype))) { 1029 | // Why replace a native EventSource ? 1030 | // https://bugzilla.mozilla.org/show_bug.cgi?id=444328 1031 | // https://bugzilla.mozilla.org/show_bug.cgi?id=831392 1032 | // https://code.google.com/p/chromium/issues/detail?id=260144 1033 | // https://code.google.com/p/chromium/issues/detail?id=225654 1034 | // ... 1035 | R = EventSourcePolyfill; 1036 | } 1037 | 1038 | (function (factory) { 1039 | { 1040 | var v = factory(exports); 1041 | if (v !== undefined) module.exports = v; 1042 | } 1043 | })(function (exports) { 1044 | exports.EventSourcePolyfill = EventSourcePolyfill; 1045 | exports.NativeEventSource = NativeEventSource; 1046 | exports.EventSource = R; 1047 | }); 1048 | }(typeof globalThis === 'undefined' ? (typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : commonjsGlobal) : globalThis)); 1049 | }); 1050 | 1051 | var eventsource$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign(/*#__PURE__*/Object.create(null), eventsource, { 1052 | 'default': eventsource 1053 | })); 1054 | 1055 | const formatText = (e) => e.data; 1056 | 1057 | const formatJSON = (e) => JSON.parse(e.data); 1058 | 1059 | class SSEClient { 1060 | constructor(config) { 1061 | this._handlers = {}; 1062 | this._listeners = {}; 1063 | this._source = null; 1064 | 1065 | if (config.format) { 1066 | if (typeof config.format === 'string') { 1067 | if (config.format === 'plain') { 1068 | this._format = formatText; 1069 | } else if (config.format === 'json') { 1070 | this._format = formatJSON; 1071 | } else { 1072 | this._format = formatText; 1073 | } 1074 | } else if (typeof config.format === 'function') { 1075 | this._format = config.format; 1076 | } else { 1077 | this._format = formatText; 1078 | } 1079 | } else { 1080 | this._format = formatText; 1081 | } 1082 | 1083 | if (config.handlers) { 1084 | for (const event in config.handlers) { 1085 | this.on(event, config.handlers[event]); 1086 | } 1087 | } 1088 | 1089 | this.url = config.url; 1090 | this.withCredentials = !!config.withCredentials; 1091 | this.polyfillOptions = config.polyfillOptions || {}; 1092 | this.forcePolyfill = !!config.forcePolyfill; 1093 | } 1094 | 1095 | get source() { 1096 | return this._source; 1097 | } 1098 | 1099 | connect() { 1100 | if (this.forcePolyfill) { 1101 | this._source = new eventsource.EventSourcePolyfill( 1102 | this.url, 1103 | Object.assign({}, this.polyfillOptions, { 1104 | withCredentials: this.withCredentials, 1105 | }), 1106 | ); 1107 | } else { 1108 | this._source = new window.EventSource(this.url, { 1109 | withCredentials: this.withCredentials, 1110 | }); 1111 | } 1112 | 1113 | return new Promise((resolve, reject) => { 1114 | this._source.onopen = () => { 1115 | // Add event listeners that were added before we connected 1116 | for (let event in this._listeners) { 1117 | this._source.addEventListener(event, this._listeners[event]); 1118 | } 1119 | 1120 | this._source.onerror = null; 1121 | 1122 | resolve(this); 1123 | }; 1124 | 1125 | this._source.onerror = reject; 1126 | }); 1127 | } 1128 | 1129 | disconnect() { 1130 | if (this._source !== null) { 1131 | this._source.close(); 1132 | this._source = null; 1133 | } 1134 | } 1135 | 1136 | on(event, handler) { 1137 | if (!event) { 1138 | // Default "event-less" event 1139 | event = 'message'; 1140 | } 1141 | 1142 | if (!this._listeners[event]) { 1143 | this._create(event); 1144 | } 1145 | 1146 | this._handlers[event].push(handler); 1147 | 1148 | return this; 1149 | } 1150 | 1151 | once(event, handler) { 1152 | this.on(event, (e) => { 1153 | this.off(event, handler); 1154 | 1155 | handler(e); 1156 | }); 1157 | 1158 | return this; 1159 | } 1160 | 1161 | off(event, handler) { 1162 | if (!this._handlers[event]) { 1163 | // no handlers registered for event 1164 | return this; 1165 | } 1166 | 1167 | const idx = this._handlers[event].indexOf(handler); 1168 | if (idx === -1) { 1169 | // handler not registered for event 1170 | return this; 1171 | } 1172 | 1173 | // remove handler from event 1174 | this._handlers[event].splice(idx, 1); 1175 | 1176 | if (this._handlers[event].length === 0) { 1177 | // remove listener since no handlers exist 1178 | this._source.removeEventListener(event, this._listeners[event]); 1179 | delete this._handlers[event]; 1180 | delete this._listeners[event]; 1181 | } 1182 | 1183 | return this; 1184 | } 1185 | 1186 | _create(event) { 1187 | this._handlers[event] = []; 1188 | 1189 | this._listeners[event] = (message) => { 1190 | let data; 1191 | 1192 | try { 1193 | data = this._format(message); 1194 | } catch (err) { 1195 | if (typeof this._source.onerror === 'function') { 1196 | this._source.onerror(err); 1197 | } 1198 | return; 1199 | } 1200 | 1201 | this._handlers[event].forEach((handler) => handler(data, message.lastEventId)); 1202 | }; 1203 | 1204 | if (this._source) { 1205 | this._source.addEventListener(event, this._listeners[event]); 1206 | } 1207 | } 1208 | } 1209 | 1210 | function install(Vue, config) { 1211 | if (Vue.config && Vue.config.globalProperties) { 1212 | // Vue3 1213 | Vue.config.globalProperties.$sse = new SSEManager(config); 1214 | } else { 1215 | // Vue2 1216 | // eslint-disable-next-line no-param-reassign, no-multi-assign 1217 | Vue.$sse = Vue.prototype.$sse = new SSEManager(config); 1218 | } 1219 | 1220 | if (config && config.polyfill) { 1221 | Promise.resolve().then(function () { return eventsource$1; }); 1222 | } 1223 | 1224 | // This mixin allows components to specify that all clients that were 1225 | // created within it should be automatically disconnected (cleanup) 1226 | // when the component is destroyed. 1227 | Vue.mixin({ 1228 | beforeCreate() { 1229 | if (this.$options.sse && this.$options.sse.cleanup) { 1230 | // We instantiate an SSEManager for this specific instance 1231 | // in order to track it (see discussions in #13 for rationale). 1232 | this.$sse = new SSEManager(); 1233 | 1234 | // We also set $clients to an empty array, as opposed to null, 1235 | // so that beforeDestroy and create know to use it. 1236 | this.$sse.$clients = []; 1237 | } 1238 | }, 1239 | beforeDestroy() { 1240 | if (this.$sse.$clients !== null) { 1241 | this.$sse.$clients.forEach((c) => c.disconnect()); 1242 | this.$sse.$clients = []; 1243 | } 1244 | }, 1245 | }); 1246 | } 1247 | 1248 | class SSEManager { 1249 | constructor(config) { 1250 | this.$defaultConfig = Object.assign( 1251 | { 1252 | format: formatText, 1253 | sendCredentials: false, 1254 | }, 1255 | config, 1256 | ); 1257 | 1258 | this.$clients = null; 1259 | } 1260 | 1261 | create(configOrURL) { 1262 | let config; 1263 | if (typeof configOrURL === 'object') { 1264 | config = configOrURL; 1265 | } else if (typeof configOrURL === 'string') { 1266 | config = { 1267 | url: configOrURL, 1268 | }; 1269 | } else { 1270 | config = {}; 1271 | } 1272 | 1273 | const client = new SSEClient(Object.assign({}, this.$defaultConfig, config)); 1274 | 1275 | // If $clients is not null, then it's array that we should push this 1276 | // client into for later cleanup in our mixin's beforeDestroy. 1277 | if (this.$clients !== null) { 1278 | this.$clients.push(client); 1279 | } 1280 | 1281 | return client; 1282 | } 1283 | } 1284 | 1285 | var index = { 1286 | install, 1287 | }; 1288 | 1289 | export default index; 1290 | export { SSEManager, install }; 1291 | -------------------------------------------------------------------------------- /dist/vue-sse.esm.browser.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-sse v2.5.0 3 | * (c) 2021 James Churchard 4 | * @license MIT 5 | */ 6 | var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{}; 7 | /** @license 8 | * eventsource.js 9 | * Available under MIT License (MIT) 10 | * https://github.com/Yaffle/EventSource/ 11 | */ 12 | var t,n=(function(t,n){!function(e){var r=e.setTimeout,o=e.clearTimeout,s=e.XMLHttpRequest,i=e.XDomainRequest,a=e.ActiveXObject,l=e.EventSource,c=e.document,u=e.Promise,h=e.fetch,d=e.Response,f=e.TextDecoder,p=e.TextEncoder,y=e.AbortController;if("undefined"==typeof window||"readyState"in c||null!=c.body||(c.readyState="loading",window.addEventListener("load",(function(e){c.readyState="complete"}),!1)),null==s&&null!=a&&(s=function(){return new a("Microsoft.XMLHTTP")}),null==Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),Date.now||(Date.now=function(){return(new Date).getTime()}),null==y){var v=h;h=function(e,t){var n=t.signal;return v(e,{headers:t.headers,credentials:t.credentials,cache:t.cache}).then((function(e){var t=e.body.getReader();return n._reader=t,n._aborted&&n._reader.cancel(),{status:e.status,statusText:e.statusText,headers:e.headers,body:{getReader:function(){return t}}}}))},y=function(){this.signal={_reader:null,_aborted:!1},this.abort=function(){null!=this.signal._reader&&this.signal._reader.cancel(),this.signal._aborted=!0}}}function g(){this.bitsNeeded=0,this.codePoint=0}g.prototype.decode=function(e){function t(e,t,n){if(1===n)return e>=128>>t&&e<=2048>>t&&e<=57344>>t&&e<=65536>>t&&e<>6>15?3:t>31?2:1;if(12===e)return t>15?3:2;if(18===e)return 3;throw new Error}for(var r=65533,o="",s=this.bitsNeeded,i=this.codePoint,a=0;a191||!t(i<<6|63&l,s-6,n(s,i)))&&(s=0,i=r,o+=String.fromCharCode(i)),0===s?(l>=0&&l<=127?(s=0,i=l):l>=192&&l<=223?(s=6,i=31&l):l>=224&&l<=239?(s=12,i=15&l):l>=240&&l<=247?(s=18,i=7&l):(s=0,i=r),0===s||t(i,s,n(s,i))||(s=0,i=r)):(s-=6,i=i<<6|63&l),0===s&&(i<=65535?o+=String.fromCharCode(i):(o+=String.fromCharCode(55296+(i-65535-1>>10)),o+=String.fromCharCode(56320+(i-65535-1&1023))))}return this.bitsNeeded=s,this.codePoint=i,o},null!=f&&null!=p&&function(){try{return"test"===(new f).decode((new p).encode("test"),{stream:!0})}catch(e){console.debug("TextDecoder does not support streaming option. Using polyfill instead: "+e)}return!1}()||(f=g);var _=function(){};function w(e){this.withCredentials=!1,this.readyState=0,this.status=0,this.statusText="",this.responseText="",this.onprogress=_,this.onload=_,this.onerror=_,this.onreadystatechange=_,this._contentType="",this._xhr=e,this._sendTimeout=0,this._abort=_}function b(e){return e.replace(/[A-Z]/g,(function(e){return String.fromCharCode(e.charCodeAt(0)+32)}))}function m(e){for(var t=Object.create(null),n=e.split("\r\n"),r=0;re.data,s=e=>JSON.parse(e.data);class i{constructor(e){if(this._handlers={},this._listeners={},this._source=null,e.format?"string"==typeof e.format?"plain"===e.format?this._format=o:"json"===e.format?this._format=s:this._format=o:"function"==typeof e.format?this._format=e.format:this._format=o:this._format=o,e.handlers)for(const t in e.handlers)this.on(t,e.handlers[t]);this.url=e.url,this.withCredentials=!!e.withCredentials,this.polyfillOptions=e.polyfillOptions||{},this.forcePolyfill=!!e.forcePolyfill}get source(){return this._source}connect(){return this.forcePolyfill?this._source=new n.EventSourcePolyfill(this.url,Object.assign({},this.polyfillOptions,{withCredentials:this.withCredentials})):this._source=new window.EventSource(this.url,{withCredentials:this.withCredentials}),new Promise(((e,t)=>{this._source.onopen=()=>{for(let e in this._listeners)this._source.addEventListener(e,this._listeners[e]);this._source.onerror=null,e(this)},this._source.onerror=t}))}disconnect(){null!==this._source&&(this._source.close(),this._source=null)}on(e,t){return e||(e="message"),this._listeners[e]||this._create(e),this._handlers[e].push(t),this}once(e,t){return this.on(e,(n=>{this.off(e,t),t(n)})),this}off(e,t){if(!this._handlers[e])return this;const n=this._handlers[e].indexOf(t);return-1===n||(this._handlers[e].splice(n,1),0===this._handlers[e].length&&(this._source.removeEventListener(e,this._listeners[e]),delete this._handlers[e],delete this._listeners[e])),this}_create(e){this._handlers[e]=[],this._listeners[e]=t=>{let n;try{n=this._format(t)}catch(e){return void("function"==typeof this._source.onerror&&this._source.onerror(e))}this._handlers[e].forEach((e=>e(n,t.lastEventId)))},this._source&&this._source.addEventListener(e,this._listeners[e])}}function a(e,t){e.config&&e.config.globalProperties?e.config.globalProperties.$sse=new l(t):e.$sse=e.prototype.$sse=new l(t),t&&t.polyfill&&Promise.resolve().then((function(){return r})),e.mixin({beforeCreate(){this.$options.sse&&this.$options.sse.cleanup&&(this.$sse=new l,this.$sse.$clients=[])},beforeDestroy(){null!==this.$sse.$clients&&(this.$sse.$clients.forEach((e=>e.disconnect())),this.$sse.$clients=[])}})}class l{constructor(e){this.$defaultConfig=Object.assign({format:o,sendCredentials:!1},e),this.$clients=null}create(e){let t;t="object"==typeof e?e:"string"==typeof e?{url:e}:{};const n=new i(Object.assign({},this.$defaultConfig,t));return null!==this.$clients&&this.$clients.push(n),n}}var c={install:a};export default c;export{l as SSEManager,a as install}; 13 | -------------------------------------------------------------------------------- /dist/vue-sse.esm.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-sse v2.5.0 3 | * (c) 2021 James Churchard 4 | * @license MIT 5 | */ 6 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 7 | 8 | function createCommonjsModule(fn) { 9 | var module = { exports: {} }; 10 | return fn(module, module.exports), module.exports; 11 | } 12 | 13 | /** @license 14 | * eventsource.js 15 | * Available under MIT License (MIT) 16 | * https://github.com/Yaffle/EventSource/ 17 | */ 18 | 19 | var eventsource = createCommonjsModule(function (module, exports) { 20 | /*jslint indent: 2, vars: true, plusplus: true */ 21 | /*global setTimeout, clearTimeout */ 22 | 23 | (function (global) { 24 | 25 | var setTimeout = global.setTimeout; 26 | var clearTimeout = global.clearTimeout; 27 | var XMLHttpRequest = global.XMLHttpRequest; 28 | var XDomainRequest = global.XDomainRequest; 29 | var ActiveXObject = global.ActiveXObject; 30 | var NativeEventSource = global.EventSource; 31 | 32 | var document = global.document; 33 | var Promise = global.Promise; 34 | var fetch = global.fetch; 35 | var Response = global.Response; 36 | var TextDecoder = global.TextDecoder; 37 | var TextEncoder = global.TextEncoder; 38 | var AbortController = global.AbortController; 39 | 40 | if (typeof window !== "undefined" && !("readyState" in document) && document.body == null) { // Firefox 2 41 | document.readyState = "loading"; 42 | window.addEventListener("load", function (event) { 43 | document.readyState = "complete"; 44 | }, false); 45 | } 46 | 47 | if (XMLHttpRequest == null && ActiveXObject != null) { // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest_in_IE6 48 | XMLHttpRequest = function () { 49 | return new ActiveXObject("Microsoft.XMLHTTP"); 50 | }; 51 | } 52 | 53 | if (Object.create == undefined) { 54 | Object.create = function (C) { 55 | function F(){} 56 | F.prototype = C; 57 | return new F(); 58 | }; 59 | } 60 | 61 | if (!Date.now) { 62 | Date.now = function now() { 63 | return new Date().getTime(); 64 | }; 65 | } 66 | 67 | // see #118 (Promise#finally with polyfilled Promise) 68 | // see #123 (data URLs crash Edge) 69 | // see #125 (CSP violations) 70 | // see pull/#138 71 | // => No way to polyfill Promise#finally 72 | 73 | if (AbortController == undefined) { 74 | var originalFetch2 = fetch; 75 | fetch = function (url, options) { 76 | var signal = options.signal; 77 | return originalFetch2(url, {headers: options.headers, credentials: options.credentials, cache: options.cache}).then(function (response) { 78 | var reader = response.body.getReader(); 79 | signal._reader = reader; 80 | if (signal._aborted) { 81 | signal._reader.cancel(); 82 | } 83 | return { 84 | status: response.status, 85 | statusText: response.statusText, 86 | headers: response.headers, 87 | body: { 88 | getReader: function () { 89 | return reader; 90 | } 91 | } 92 | }; 93 | }); 94 | }; 95 | AbortController = function () { 96 | this.signal = { 97 | _reader: null, 98 | _aborted: false 99 | }; 100 | this.abort = function () { 101 | if (this.signal._reader != null) { 102 | this.signal._reader.cancel(); 103 | } 104 | this.signal._aborted = true; 105 | }; 106 | }; 107 | } 108 | 109 | function TextDecoderPolyfill() { 110 | this.bitsNeeded = 0; 111 | this.codePoint = 0; 112 | } 113 | 114 | TextDecoderPolyfill.prototype.decode = function (octets) { 115 | function valid(codePoint, shift, octetsCount) { 116 | if (octetsCount === 1) { 117 | return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07FF; 118 | } 119 | if (octetsCount === 2) { 120 | return codePoint >= 0x0800 >> shift && codePoint << shift <= 0xD7FF || codePoint >= 0xE000 >> shift && codePoint << shift <= 0xFFFF; 121 | } 122 | if (octetsCount === 3) { 123 | return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10FFFF; 124 | } 125 | throw new Error(); 126 | } 127 | function octetsCount(bitsNeeded, codePoint) { 128 | if (bitsNeeded === 6 * 1) { 129 | return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1; 130 | } 131 | if (bitsNeeded === 6 * 2) { 132 | return codePoint > 15 ? 3 : 2; 133 | } 134 | if (bitsNeeded === 6 * 3) { 135 | return 3; 136 | } 137 | throw new Error(); 138 | } 139 | var REPLACER = 0xFFFD; 140 | var string = ""; 141 | var bitsNeeded = this.bitsNeeded; 142 | var codePoint = this.codePoint; 143 | for (var i = 0; i < octets.length; i += 1) { 144 | var octet = octets[i]; 145 | if (bitsNeeded !== 0) { 146 | if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) { 147 | bitsNeeded = 0; 148 | codePoint = REPLACER; 149 | string += String.fromCharCode(codePoint); 150 | } 151 | } 152 | if (bitsNeeded === 0) { 153 | if (octet >= 0 && octet <= 127) { 154 | bitsNeeded = 0; 155 | codePoint = octet; 156 | } else if (octet >= 192 && octet <= 223) { 157 | bitsNeeded = 6 * 1; 158 | codePoint = octet & 31; 159 | } else if (octet >= 224 && octet <= 239) { 160 | bitsNeeded = 6 * 2; 161 | codePoint = octet & 15; 162 | } else if (octet >= 240 && octet <= 247) { 163 | bitsNeeded = 6 * 3; 164 | codePoint = octet & 7; 165 | } else { 166 | bitsNeeded = 0; 167 | codePoint = REPLACER; 168 | } 169 | if (bitsNeeded !== 0 && !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint))) { 170 | bitsNeeded = 0; 171 | codePoint = REPLACER; 172 | } 173 | } else { 174 | bitsNeeded -= 6; 175 | codePoint = codePoint << 6 | octet & 63; 176 | } 177 | if (bitsNeeded === 0) { 178 | if (codePoint <= 0xFFFF) { 179 | string += String.fromCharCode(codePoint); 180 | } else { 181 | string += String.fromCharCode(0xD800 + (codePoint - 0xFFFF - 1 >> 10)); 182 | string += String.fromCharCode(0xDC00 + (codePoint - 0xFFFF - 1 & 0x3FF)); 183 | } 184 | } 185 | } 186 | this.bitsNeeded = bitsNeeded; 187 | this.codePoint = codePoint; 188 | return string; 189 | }; 190 | 191 | // Firefox < 38 throws an error with stream option 192 | var supportsStreamOption = function () { 193 | try { 194 | return new TextDecoder().decode(new TextEncoder().encode("test"), {stream: true}) === "test"; 195 | } catch (error) { 196 | console.debug("TextDecoder does not support streaming option. Using polyfill instead: " + error); 197 | } 198 | return false; 199 | }; 200 | 201 | // IE, Edge 202 | if (TextDecoder == undefined || TextEncoder == undefined || !supportsStreamOption()) { 203 | TextDecoder = TextDecoderPolyfill; 204 | } 205 | 206 | var k = function () { 207 | }; 208 | 209 | function XHRWrapper(xhr) { 210 | this.withCredentials = false; 211 | this.readyState = 0; 212 | this.status = 0; 213 | this.statusText = ""; 214 | this.responseText = ""; 215 | this.onprogress = k; 216 | this.onload = k; 217 | this.onerror = k; 218 | this.onreadystatechange = k; 219 | this._contentType = ""; 220 | this._xhr = xhr; 221 | this._sendTimeout = 0; 222 | this._abort = k; 223 | } 224 | 225 | XHRWrapper.prototype.open = function (method, url) { 226 | this._abort(true); 227 | 228 | var that = this; 229 | var xhr = this._xhr; 230 | var state = 1; 231 | var timeout = 0; 232 | 233 | this._abort = function (silent) { 234 | if (that._sendTimeout !== 0) { 235 | clearTimeout(that._sendTimeout); 236 | that._sendTimeout = 0; 237 | } 238 | if (state === 1 || state === 2 || state === 3) { 239 | state = 4; 240 | xhr.onload = k; 241 | xhr.onerror = k; 242 | xhr.onabort = k; 243 | xhr.onprogress = k; 244 | xhr.onreadystatechange = k; 245 | // IE 8 - 9: XDomainRequest#abort() does not fire any event 246 | // Opera < 10: XMLHttpRequest#abort() does not fire any event 247 | xhr.abort(); 248 | if (timeout !== 0) { 249 | clearTimeout(timeout); 250 | timeout = 0; 251 | } 252 | if (!silent) { 253 | that.readyState = 4; 254 | that.onabort(null); 255 | that.onreadystatechange(); 256 | } 257 | } 258 | state = 0; 259 | }; 260 | 261 | var onStart = function () { 262 | if (state === 1) { 263 | //state = 2; 264 | var status = 0; 265 | var statusText = ""; 266 | var contentType = undefined; 267 | if (!("contentType" in xhr)) { 268 | try { 269 | status = xhr.status; 270 | statusText = xhr.statusText; 271 | contentType = xhr.getResponseHeader("Content-Type"); 272 | } catch (error) { 273 | // IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3 274 | // Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2 275 | // https://bugs.webkit.org/show_bug.cgi?id=29121 276 | status = 0; 277 | statusText = ""; 278 | contentType = undefined; 279 | // Firefox < 14, Chrome ?, Safari ? 280 | // https://bugs.webkit.org/show_bug.cgi?id=29658 281 | // https://bugs.webkit.org/show_bug.cgi?id=77854 282 | } 283 | } else { 284 | status = 200; 285 | statusText = "OK"; 286 | contentType = xhr.contentType; 287 | } 288 | if (status !== 0) { 289 | state = 2; 290 | that.readyState = 2; 291 | that.status = status; 292 | that.statusText = statusText; 293 | that._contentType = contentType; 294 | that.onreadystatechange(); 295 | } 296 | } 297 | }; 298 | var onProgress = function () { 299 | onStart(); 300 | if (state === 2 || state === 3) { 301 | state = 3; 302 | var responseText = ""; 303 | try { 304 | responseText = xhr.responseText; 305 | } catch (error) { 306 | // IE 8 - 9 with XMLHttpRequest 307 | } 308 | that.readyState = 3; 309 | that.responseText = responseText; 310 | that.onprogress(); 311 | } 312 | }; 313 | var onFinish = function (type, event) { 314 | if (event == null || event.preventDefault == null) { 315 | event = { 316 | preventDefault: k 317 | }; 318 | } 319 | // Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3) 320 | // IE 8 fires "onload" without "onprogress" 321 | onProgress(); 322 | if (state === 1 || state === 2 || state === 3) { 323 | state = 4; 324 | if (timeout !== 0) { 325 | clearTimeout(timeout); 326 | timeout = 0; 327 | } 328 | that.readyState = 4; 329 | if (type === "load") { 330 | that.onload(event); 331 | } else if (type === "error") { 332 | that.onerror(event); 333 | } else if (type === "abort") { 334 | that.onabort(event); 335 | } else { 336 | throw new TypeError(); 337 | } 338 | that.onreadystatechange(); 339 | } 340 | }; 341 | var onReadyStateChange = function (event) { 342 | if (xhr != undefined) { // Opera 12 343 | if (xhr.readyState === 4) { 344 | if (!("onload" in xhr) || !("onerror" in xhr) || !("onabort" in xhr)) { 345 | onFinish(xhr.responseText === "" ? "error" : "load", event); 346 | } 347 | } else if (xhr.readyState === 3) { 348 | if (!("onprogress" in xhr)) { // testing XMLHttpRequest#responseText too many times is too slow in IE 11 349 | // and in Firefox 3.6 350 | onProgress(); 351 | } 352 | } else if (xhr.readyState === 2) { 353 | onStart(); 354 | } 355 | } 356 | }; 357 | var onTimeout = function () { 358 | timeout = setTimeout(function () { 359 | onTimeout(); 360 | }, 500); 361 | if (xhr.readyState === 3) { 362 | onProgress(); 363 | } 364 | }; 365 | 366 | // XDomainRequest#abort removes onprogress, onerror, onload 367 | if ("onload" in xhr) { 368 | xhr.onload = function (event) { 369 | onFinish("load", event); 370 | }; 371 | } 372 | if ("onerror" in xhr) { 373 | xhr.onerror = function (event) { 374 | onFinish("error", event); 375 | }; 376 | } 377 | // improper fix to match Firefox behaviour, but it is better than just ignore abort 378 | // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596 379 | // https://bugzilla.mozilla.org/show_bug.cgi?id=880200 380 | // https://code.google.com/p/chromium/issues/detail?id=153570 381 | // IE 8 fires "onload" without "onprogress 382 | if ("onabort" in xhr) { 383 | xhr.onabort = function (event) { 384 | onFinish("abort", event); 385 | }; 386 | } 387 | 388 | if ("onprogress" in xhr) { 389 | xhr.onprogress = onProgress; 390 | } 391 | 392 | // IE 8 - 9 (XMLHTTPRequest) 393 | // Opera < 12 394 | // Firefox < 3.5 395 | // Firefox 3.5 - 3.6 - ? < 9.0 396 | // onprogress is not fired sometimes or delayed 397 | // see also #64 (significant lag in IE 11) 398 | if ("onreadystatechange" in xhr) { 399 | xhr.onreadystatechange = function (event) { 400 | onReadyStateChange(event); 401 | }; 402 | } 403 | 404 | if ("contentType" in xhr || !("ontimeout" in XMLHttpRequest.prototype)) { 405 | url += (url.indexOf("?") === -1 ? "?" : "&") + "padding=true"; 406 | } 407 | xhr.open(method, url, true); 408 | 409 | if ("readyState" in xhr) { 410 | // workaround for Opera 12 issue with "progress" events 411 | // #91 (XMLHttpRequest onprogress not fired for streaming response in Edge 14-15-?) 412 | timeout = setTimeout(function () { 413 | onTimeout(); 414 | }, 0); 415 | } 416 | }; 417 | XHRWrapper.prototype.abort = function () { 418 | this._abort(false); 419 | }; 420 | XHRWrapper.prototype.getResponseHeader = function (name) { 421 | return this._contentType; 422 | }; 423 | XHRWrapper.prototype.setRequestHeader = function (name, value) { 424 | var xhr = this._xhr; 425 | if ("setRequestHeader" in xhr) { 426 | xhr.setRequestHeader(name, value); 427 | } 428 | }; 429 | XHRWrapper.prototype.getAllResponseHeaders = function () { 430 | // XMLHttpRequest#getAllResponseHeaders returns null for CORS requests in Firefox 3.6.28 431 | return this._xhr.getAllResponseHeaders != undefined ? this._xhr.getAllResponseHeaders() || "" : ""; 432 | }; 433 | XHRWrapper.prototype.send = function () { 434 | // loading indicator in Safari < ? (6), Chrome < 14, Firefox 435 | // https://bugzilla.mozilla.org/show_bug.cgi?id=736723 436 | if ((!("ontimeout" in XMLHttpRequest.prototype) || (!("sendAsBinary" in XMLHttpRequest.prototype) && !("mozAnon" in XMLHttpRequest.prototype))) && 437 | document != undefined && 438 | document.readyState != undefined && 439 | document.readyState !== "complete") { 440 | var that = this; 441 | that._sendTimeout = setTimeout(function () { 442 | that._sendTimeout = 0; 443 | that.send(); 444 | }, 4); 445 | return; 446 | } 447 | 448 | var xhr = this._xhr; 449 | // withCredentials should be set after "open" for Safari and Chrome (< 19 ?) 450 | if ("withCredentials" in xhr) { 451 | xhr.withCredentials = this.withCredentials; 452 | } 453 | try { 454 | // xhr.send(); throws "Not enough arguments" in Firefox 3.0 455 | xhr.send(undefined); 456 | } catch (error1) { 457 | // Safari 5.1.7, Opera 12 458 | throw error1; 459 | } 460 | }; 461 | 462 | function toLowerCase(name) { 463 | return name.replace(/[A-Z]/g, function (c) { 464 | return String.fromCharCode(c.charCodeAt(0) + 0x20); 465 | }); 466 | } 467 | 468 | function HeadersPolyfill(all) { 469 | // Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example 470 | var map = Object.create(null); 471 | var array = all.split("\r\n"); 472 | for (var i = 0; i < array.length; i += 1) { 473 | var line = array[i]; 474 | var parts = line.split(": "); 475 | var name = parts.shift(); 476 | var value = parts.join(": "); 477 | map[toLowerCase(name)] = value; 478 | } 479 | this._map = map; 480 | } 481 | HeadersPolyfill.prototype.get = function (name) { 482 | return this._map[toLowerCase(name)]; 483 | }; 484 | 485 | if (XMLHttpRequest != null && XMLHttpRequest.HEADERS_RECEIVED == null) { // IE < 9, Firefox 3.6 486 | XMLHttpRequest.HEADERS_RECEIVED = 2; 487 | } 488 | 489 | function XHRTransport() { 490 | } 491 | 492 | XHRTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 493 | xhr.open("GET", url); 494 | var offset = 0; 495 | xhr.onprogress = function () { 496 | var responseText = xhr.responseText; 497 | var chunk = responseText.slice(offset); 498 | offset += chunk.length; 499 | onProgressCallback(chunk); 500 | }; 501 | xhr.onerror = function (event) { 502 | event.preventDefault(); 503 | onFinishCallback(new Error("NetworkError")); 504 | }; 505 | xhr.onload = function () { 506 | onFinishCallback(null); 507 | }; 508 | xhr.onabort = function () { 509 | onFinishCallback(null); 510 | }; 511 | xhr.onreadystatechange = function () { 512 | if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) { 513 | var status = xhr.status; 514 | var statusText = xhr.statusText; 515 | var contentType = xhr.getResponseHeader("Content-Type"); 516 | var headers = xhr.getAllResponseHeaders(); 517 | onStartCallback(status, statusText, contentType, new HeadersPolyfill(headers)); 518 | } 519 | }; 520 | xhr.withCredentials = withCredentials; 521 | for (var name in headers) { 522 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 523 | xhr.setRequestHeader(name, headers[name]); 524 | } 525 | } 526 | xhr.send(); 527 | return xhr; 528 | }; 529 | 530 | function HeadersWrapper(headers) { 531 | this._headers = headers; 532 | } 533 | HeadersWrapper.prototype.get = function (name) { 534 | return this._headers.get(name); 535 | }; 536 | 537 | function FetchTransport() { 538 | } 539 | 540 | FetchTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) { 541 | var reader = null; 542 | var controller = new AbortController(); 543 | var signal = controller.signal; 544 | var textDecoder = new TextDecoder(); 545 | fetch(url, { 546 | headers: headers, 547 | credentials: withCredentials ? "include" : "same-origin", 548 | signal: signal, 549 | cache: "no-store" 550 | }).then(function (response) { 551 | reader = response.body.getReader(); 552 | onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"), new HeadersWrapper(response.headers)); 553 | // see https://github.com/promises-aplus/promises-spec/issues/179 554 | return new Promise(function (resolve, reject) { 555 | var readNextChunk = function () { 556 | reader.read().then(function (result) { 557 | if (result.done) { 558 | //Note: bytes in textDecoder are ignored 559 | resolve(undefined); 560 | } else { 561 | var chunk = textDecoder.decode(result.value, {stream: true}); 562 | onProgressCallback(chunk); 563 | readNextChunk(); 564 | } 565 | })["catch"](function (error) { 566 | reject(error); 567 | }); 568 | }; 569 | readNextChunk(); 570 | }); 571 | })["catch"](function (error) { 572 | if (error.name === "AbortError") { 573 | return undefined; 574 | } else { 575 | return error; 576 | } 577 | }).then(function (error) { 578 | onFinishCallback(error); 579 | }); 580 | return { 581 | abort: function () { 582 | if (reader != null) { 583 | reader.cancel(); // https://bugzilla.mozilla.org/show_bug.cgi?id=1583815 584 | } 585 | controller.abort(); 586 | } 587 | }; 588 | }; 589 | 590 | function EventTarget() { 591 | this._listeners = Object.create(null); 592 | } 593 | 594 | function throwError(e) { 595 | setTimeout(function () { 596 | throw e; 597 | }, 0); 598 | } 599 | 600 | EventTarget.prototype.dispatchEvent = function (event) { 601 | event.target = this; 602 | var typeListeners = this._listeners[event.type]; 603 | if (typeListeners != undefined) { 604 | var length = typeListeners.length; 605 | for (var i = 0; i < length; i += 1) { 606 | var listener = typeListeners[i]; 607 | try { 608 | if (typeof listener.handleEvent === "function") { 609 | listener.handleEvent(event); 610 | } else { 611 | listener.call(this, event); 612 | } 613 | } catch (e) { 614 | throwError(e); 615 | } 616 | } 617 | } 618 | }; 619 | EventTarget.prototype.addEventListener = function (type, listener) { 620 | type = String(type); 621 | var listeners = this._listeners; 622 | var typeListeners = listeners[type]; 623 | if (typeListeners == undefined) { 624 | typeListeners = []; 625 | listeners[type] = typeListeners; 626 | } 627 | var found = false; 628 | for (var i = 0; i < typeListeners.length; i += 1) { 629 | if (typeListeners[i] === listener) { 630 | found = true; 631 | } 632 | } 633 | if (!found) { 634 | typeListeners.push(listener); 635 | } 636 | }; 637 | EventTarget.prototype.removeEventListener = function (type, listener) { 638 | type = String(type); 639 | var listeners = this._listeners; 640 | var typeListeners = listeners[type]; 641 | if (typeListeners != undefined) { 642 | var filtered = []; 643 | for (var i = 0; i < typeListeners.length; i += 1) { 644 | if (typeListeners[i] !== listener) { 645 | filtered.push(typeListeners[i]); 646 | } 647 | } 648 | if (filtered.length === 0) { 649 | delete listeners[type]; 650 | } else { 651 | listeners[type] = filtered; 652 | } 653 | } 654 | }; 655 | 656 | function Event(type) { 657 | this.type = type; 658 | this.target = undefined; 659 | } 660 | 661 | function MessageEvent(type, options) { 662 | Event.call(this, type); 663 | this.data = options.data; 664 | this.lastEventId = options.lastEventId; 665 | } 666 | 667 | MessageEvent.prototype = Object.create(Event.prototype); 668 | 669 | function ConnectionEvent(type, options) { 670 | Event.call(this, type); 671 | this.status = options.status; 672 | this.statusText = options.statusText; 673 | this.headers = options.headers; 674 | } 675 | 676 | ConnectionEvent.prototype = Object.create(Event.prototype); 677 | 678 | function ErrorEvent(type, options) { 679 | Event.call(this, type); 680 | this.error = options.error; 681 | } 682 | 683 | ErrorEvent.prototype = Object.create(Event.prototype); 684 | 685 | var WAITING = -1; 686 | var CONNECTING = 0; 687 | var OPEN = 1; 688 | var CLOSED = 2; 689 | 690 | var AFTER_CR = -1; 691 | var FIELD_START = 0; 692 | var FIELD = 1; 693 | var VALUE_START = 2; 694 | var VALUE = 3; 695 | 696 | var contentTypeRegExp = /^text\/event\-stream(;.*)?$/i; 697 | 698 | var MINIMUM_DURATION = 1000; 699 | var MAXIMUM_DURATION = 18000000; 700 | 701 | var parseDuration = function (value, def) { 702 | var n = value == null ? def : parseInt(value, 10); 703 | if (n !== n) { 704 | n = def; 705 | } 706 | return clampDuration(n); 707 | }; 708 | var clampDuration = function (n) { 709 | return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION); 710 | }; 711 | 712 | var fire = function (that, f, event) { 713 | try { 714 | if (typeof f === "function") { 715 | f.call(that, event); 716 | } 717 | } catch (e) { 718 | throwError(e); 719 | } 720 | }; 721 | 722 | function EventSourcePolyfill(url, options) { 723 | EventTarget.call(this); 724 | options = options || {}; 725 | 726 | this.onopen = undefined; 727 | this.onmessage = undefined; 728 | this.onerror = undefined; 729 | 730 | this.url = undefined; 731 | this.readyState = undefined; 732 | this.withCredentials = undefined; 733 | this.headers = undefined; 734 | 735 | this._close = undefined; 736 | 737 | start(this, url, options); 738 | } 739 | 740 | function getBestXHRTransport() { 741 | return (XMLHttpRequest != undefined && ("withCredentials" in XMLHttpRequest.prototype)) || XDomainRequest == undefined 742 | ? new XMLHttpRequest() 743 | : new XDomainRequest(); 744 | } 745 | 746 | var isFetchSupported = fetch != undefined && Response != undefined && "body" in Response.prototype; 747 | 748 | function start(es, url, options) { 749 | url = String(url); 750 | var withCredentials = Boolean(options.withCredentials); 751 | var lastEventIdQueryParameterName = options.lastEventIdQueryParameterName || "lastEventId"; 752 | 753 | var initialRetry = clampDuration(1000); 754 | var heartbeatTimeout = parseDuration(options.heartbeatTimeout, 45000); 755 | 756 | var lastEventId = ""; 757 | var retry = initialRetry; 758 | var wasActivity = false; 759 | var textLength = 0; 760 | var headers = options.headers || {}; 761 | var TransportOption = options.Transport; 762 | var xhr = isFetchSupported && TransportOption == undefined ? undefined : new XHRWrapper(TransportOption != undefined ? new TransportOption() : getBestXHRTransport()); 763 | var transport = TransportOption != null && typeof TransportOption !== "string" ? new TransportOption() : (xhr == undefined ? new FetchTransport() : new XHRTransport()); 764 | var abortController = undefined; 765 | var timeout = 0; 766 | var currentState = WAITING; 767 | var dataBuffer = ""; 768 | var lastEventIdBuffer = ""; 769 | var eventTypeBuffer = ""; 770 | 771 | var textBuffer = ""; 772 | var state = FIELD_START; 773 | var fieldStart = 0; 774 | var valueStart = 0; 775 | 776 | var onStart = function (status, statusText, contentType, headers) { 777 | if (currentState === CONNECTING) { 778 | if (status === 200 && contentType != undefined && contentTypeRegExp.test(contentType)) { 779 | currentState = OPEN; 780 | wasActivity = Date.now(); 781 | retry = initialRetry; 782 | es.readyState = OPEN; 783 | var event = new ConnectionEvent("open", { 784 | status: status, 785 | statusText: statusText, 786 | headers: headers 787 | }); 788 | es.dispatchEvent(event); 789 | fire(es, es.onopen, event); 790 | } else { 791 | var message = ""; 792 | if (status !== 200) { 793 | if (statusText) { 794 | statusText = statusText.replace(/\s+/g, " "); 795 | } 796 | message = "EventSource's response has a status " + status + " " + statusText + " that is not 200. Aborting the connection."; 797 | } else { 798 | message = "EventSource's response has a Content-Type specifying an unsupported type: " + (contentType == undefined ? "-" : contentType.replace(/\s+/g, " ")) + ". Aborting the connection."; 799 | } 800 | close(); 801 | var event = new ConnectionEvent("error", { 802 | status: status, 803 | statusText: statusText, 804 | headers: headers 805 | }); 806 | es.dispatchEvent(event); 807 | fire(es, es.onerror, event); 808 | console.error(message); 809 | } 810 | } 811 | }; 812 | 813 | var onProgress = function (textChunk) { 814 | if (currentState === OPEN) { 815 | var n = -1; 816 | for (var i = 0; i < textChunk.length; i += 1) { 817 | var c = textChunk.charCodeAt(i); 818 | if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) { 819 | n = i; 820 | } 821 | } 822 | var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1); 823 | textBuffer = (n === -1 ? textBuffer : "") + textChunk.slice(n + 1); 824 | if (textChunk !== "") { 825 | wasActivity = Date.now(); 826 | textLength += textChunk.length; 827 | } 828 | for (var position = 0; position < chunk.length; position += 1) { 829 | var c = chunk.charCodeAt(position); 830 | if (state === AFTER_CR && c === "\n".charCodeAt(0)) { 831 | state = FIELD_START; 832 | } else { 833 | if (state === AFTER_CR) { 834 | state = FIELD_START; 835 | } 836 | if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) { 837 | if (state !== FIELD_START) { 838 | if (state === FIELD) { 839 | valueStart = position + 1; 840 | } 841 | var field = chunk.slice(fieldStart, valueStart - 1); 842 | var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === " ".charCodeAt(0) ? 1 : 0), position); 843 | if (field === "data") { 844 | dataBuffer += "\n"; 845 | dataBuffer += value; 846 | } else if (field === "id") { 847 | lastEventIdBuffer = value; 848 | } else if (field === "event") { 849 | eventTypeBuffer = value; 850 | } else if (field === "retry") { 851 | initialRetry = parseDuration(value, initialRetry); 852 | retry = initialRetry; 853 | } else if (field === "heartbeatTimeout") { 854 | heartbeatTimeout = parseDuration(value, heartbeatTimeout); 855 | if (timeout !== 0) { 856 | clearTimeout(timeout); 857 | timeout = setTimeout(function () { 858 | onTimeout(); 859 | }, heartbeatTimeout); 860 | } 861 | } 862 | } 863 | if (state === FIELD_START) { 864 | if (dataBuffer !== "") { 865 | lastEventId = lastEventIdBuffer; 866 | if (eventTypeBuffer === "") { 867 | eventTypeBuffer = "message"; 868 | } 869 | var event = new MessageEvent(eventTypeBuffer, { 870 | data: dataBuffer.slice(1), 871 | lastEventId: lastEventIdBuffer 872 | }); 873 | es.dispatchEvent(event); 874 | if (eventTypeBuffer === "open") { 875 | fire(es, es.onopen, event); 876 | } else if (eventTypeBuffer === "message") { 877 | fire(es, es.onmessage, event); 878 | } else if (eventTypeBuffer === "error") { 879 | fire(es, es.onerror, event); 880 | } 881 | if (currentState === CLOSED) { 882 | return; 883 | } 884 | } 885 | dataBuffer = ""; 886 | eventTypeBuffer = ""; 887 | } 888 | state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START; 889 | } else { 890 | if (state === FIELD_START) { 891 | fieldStart = position; 892 | state = FIELD; 893 | } 894 | if (state === FIELD) { 895 | if (c === ":".charCodeAt(0)) { 896 | valueStart = position + 1; 897 | state = VALUE_START; 898 | } 899 | } else if (state === VALUE_START) { 900 | state = VALUE; 901 | } 902 | } 903 | } 904 | } 905 | } 906 | }; 907 | 908 | var onFinish = function (error) { 909 | if (currentState === OPEN || currentState === CONNECTING) { 910 | currentState = WAITING; 911 | if (timeout !== 0) { 912 | clearTimeout(timeout); 913 | timeout = 0; 914 | } 915 | timeout = setTimeout(function () { 916 | onTimeout(); 917 | }, retry); 918 | retry = clampDuration(Math.min(initialRetry * 16, retry * 2)); 919 | 920 | es.readyState = CONNECTING; 921 | var event = new ErrorEvent("error", {error: error}); 922 | es.dispatchEvent(event); 923 | fire(es, es.onerror, event); 924 | if (error != undefined) { 925 | console.error(error); 926 | } 927 | } 928 | }; 929 | 930 | var close = function () { 931 | currentState = CLOSED; 932 | if (abortController != undefined) { 933 | abortController.abort(); 934 | abortController = undefined; 935 | } 936 | if (timeout !== 0) { 937 | clearTimeout(timeout); 938 | timeout = 0; 939 | } 940 | es.readyState = CLOSED; 941 | }; 942 | 943 | var onTimeout = function () { 944 | timeout = 0; 945 | 946 | if (currentState !== WAITING) { 947 | if (!wasActivity && abortController != undefined) { 948 | onFinish(new Error("No activity within " + heartbeatTimeout + " milliseconds." + " " + (currentState === CONNECTING ? "No response received." : textLength + " chars received.") + " " + "Reconnecting.")); 949 | if (abortController != undefined) { 950 | abortController.abort(); 951 | abortController = undefined; 952 | } 953 | } else { 954 | var nextHeartbeat = Math.max((wasActivity || Date.now()) + heartbeatTimeout - Date.now(), 1); 955 | wasActivity = false; 956 | timeout = setTimeout(function () { 957 | onTimeout(); 958 | }, nextHeartbeat); 959 | } 960 | return; 961 | } 962 | 963 | wasActivity = false; 964 | textLength = 0; 965 | timeout = setTimeout(function () { 966 | onTimeout(); 967 | }, heartbeatTimeout); 968 | 969 | currentState = CONNECTING; 970 | dataBuffer = ""; 971 | eventTypeBuffer = ""; 972 | lastEventIdBuffer = lastEventId; 973 | textBuffer = ""; 974 | fieldStart = 0; 975 | valueStart = 0; 976 | state = FIELD_START; 977 | 978 | // https://bugzilla.mozilla.org/show_bug.cgi?id=428916 979 | // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers. 980 | var requestURL = url; 981 | if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") { 982 | if (lastEventId !== "") { 983 | requestURL += (url.indexOf("?") === -1 ? "?" : "&") + lastEventIdQueryParameterName +"=" + encodeURIComponent(lastEventId); 984 | } 985 | } 986 | var withCredentials = es.withCredentials; 987 | var requestHeaders = {}; 988 | requestHeaders["Accept"] = "text/event-stream"; 989 | var headers = es.headers; 990 | if (headers != undefined) { 991 | for (var name in headers) { 992 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 993 | requestHeaders[name] = headers[name]; 994 | } 995 | } 996 | } 997 | try { 998 | abortController = transport.open(xhr, onStart, onProgress, onFinish, requestURL, withCredentials, requestHeaders); 999 | } catch (error) { 1000 | close(); 1001 | throw error; 1002 | } 1003 | }; 1004 | 1005 | es.url = url; 1006 | es.readyState = CONNECTING; 1007 | es.withCredentials = withCredentials; 1008 | es.headers = headers; 1009 | es._close = close; 1010 | 1011 | onTimeout(); 1012 | } 1013 | 1014 | EventSourcePolyfill.prototype = Object.create(EventTarget.prototype); 1015 | EventSourcePolyfill.prototype.CONNECTING = CONNECTING; 1016 | EventSourcePolyfill.prototype.OPEN = OPEN; 1017 | EventSourcePolyfill.prototype.CLOSED = CLOSED; 1018 | EventSourcePolyfill.prototype.close = function () { 1019 | this._close(); 1020 | }; 1021 | 1022 | EventSourcePolyfill.CONNECTING = CONNECTING; 1023 | EventSourcePolyfill.OPEN = OPEN; 1024 | EventSourcePolyfill.CLOSED = CLOSED; 1025 | EventSourcePolyfill.prototype.withCredentials = undefined; 1026 | 1027 | var R = NativeEventSource; 1028 | if (XMLHttpRequest != undefined && (NativeEventSource == undefined || !("withCredentials" in NativeEventSource.prototype))) { 1029 | // Why replace a native EventSource ? 1030 | // https://bugzilla.mozilla.org/show_bug.cgi?id=444328 1031 | // https://bugzilla.mozilla.org/show_bug.cgi?id=831392 1032 | // https://code.google.com/p/chromium/issues/detail?id=260144 1033 | // https://code.google.com/p/chromium/issues/detail?id=225654 1034 | // ... 1035 | R = EventSourcePolyfill; 1036 | } 1037 | 1038 | (function (factory) { 1039 | { 1040 | var v = factory(exports); 1041 | if (v !== undefined) { module.exports = v; } 1042 | } 1043 | })(function (exports) { 1044 | exports.EventSourcePolyfill = EventSourcePolyfill; 1045 | exports.NativeEventSource = NativeEventSource; 1046 | exports.EventSource = R; 1047 | }); 1048 | }(typeof globalThis === 'undefined' ? (typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : commonjsGlobal) : globalThis)); 1049 | }); 1050 | 1051 | var eventsource$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign(/*#__PURE__*/Object.create(null), eventsource, { 1052 | 'default': eventsource 1053 | })); 1054 | 1055 | var formatText = function (e) { return e.data; }; 1056 | 1057 | var formatJSON = function (e) { return JSON.parse(e.data); }; 1058 | 1059 | var SSEClient = function SSEClient(config) { 1060 | this._handlers = {}; 1061 | this._listeners = {}; 1062 | this._source = null; 1063 | 1064 | if (config.format) { 1065 | if (typeof config.format === 'string') { 1066 | if (config.format === 'plain') { 1067 | this._format = formatText; 1068 | } else if (config.format === 'json') { 1069 | this._format = formatJSON; 1070 | } else { 1071 | this._format = formatText; 1072 | } 1073 | } else if (typeof config.format === 'function') { 1074 | this._format = config.format; 1075 | } else { 1076 | this._format = formatText; 1077 | } 1078 | } else { 1079 | this._format = formatText; 1080 | } 1081 | 1082 | if (config.handlers) { 1083 | for (var event in config.handlers) { 1084 | this.on(event, config.handlers[event]); 1085 | } 1086 | } 1087 | 1088 | this.url = config.url; 1089 | this.withCredentials = !!config.withCredentials; 1090 | this.polyfillOptions = config.polyfillOptions || {}; 1091 | this.forcePolyfill = !!config.forcePolyfill; 1092 | }; 1093 | 1094 | var prototypeAccessors = { source: { configurable: true } }; 1095 | 1096 | prototypeAccessors.source.get = function () { 1097 | return this._source; 1098 | }; 1099 | 1100 | SSEClient.prototype.connect = function connect () { 1101 | var this$1 = this; 1102 | 1103 | if (this.forcePolyfill) { 1104 | this._source = new eventsource.EventSourcePolyfill( 1105 | this.url, 1106 | Object.assign({}, this.polyfillOptions, { 1107 | withCredentials: this.withCredentials, 1108 | }) 1109 | ); 1110 | } else { 1111 | this._source = new window.EventSource(this.url, { 1112 | withCredentials: this.withCredentials, 1113 | }); 1114 | } 1115 | 1116 | return new Promise(function (resolve, reject) { 1117 | this$1._source.onopen = function () { 1118 | // Add event listeners that were added before we connected 1119 | for (var event in this$1._listeners) { 1120 | this$1._source.addEventListener(event, this$1._listeners[event]); 1121 | } 1122 | 1123 | this$1._source.onerror = null; 1124 | 1125 | resolve(this$1); 1126 | }; 1127 | 1128 | this$1._source.onerror = reject; 1129 | }); 1130 | }; 1131 | 1132 | SSEClient.prototype.disconnect = function disconnect () { 1133 | if (this._source !== null) { 1134 | this._source.close(); 1135 | this._source = null; 1136 | } 1137 | }; 1138 | 1139 | SSEClient.prototype.on = function on (event, handler) { 1140 | if (!event) { 1141 | // Default "event-less" event 1142 | event = 'message'; 1143 | } 1144 | 1145 | if (!this._listeners[event]) { 1146 | this._create(event); 1147 | } 1148 | 1149 | this._handlers[event].push(handler); 1150 | 1151 | return this; 1152 | }; 1153 | 1154 | SSEClient.prototype.once = function once (event, handler) { 1155 | var this$1 = this; 1156 | 1157 | this.on(event, function (e) { 1158 | this$1.off(event, handler); 1159 | 1160 | handler(e); 1161 | }); 1162 | 1163 | return this; 1164 | }; 1165 | 1166 | SSEClient.prototype.off = function off (event, handler) { 1167 | if (!this._handlers[event]) { 1168 | // no handlers registered for event 1169 | return this; 1170 | } 1171 | 1172 | var idx = this._handlers[event].indexOf(handler); 1173 | if (idx === -1) { 1174 | // handler not registered for event 1175 | return this; 1176 | } 1177 | 1178 | // remove handler from event 1179 | this._handlers[event].splice(idx, 1); 1180 | 1181 | if (this._handlers[event].length === 0) { 1182 | // remove listener since no handlers exist 1183 | this._source.removeEventListener(event, this._listeners[event]); 1184 | delete this._handlers[event]; 1185 | delete this._listeners[event]; 1186 | } 1187 | 1188 | return this; 1189 | }; 1190 | 1191 | SSEClient.prototype._create = function _create (event) { 1192 | var this$1 = this; 1193 | 1194 | this._handlers[event] = []; 1195 | 1196 | this._listeners[event] = function (message) { 1197 | var data; 1198 | 1199 | try { 1200 | data = this$1._format(message); 1201 | } catch (err) { 1202 | if (typeof this$1._source.onerror === 'function') { 1203 | this$1._source.onerror(err); 1204 | } 1205 | return; 1206 | } 1207 | 1208 | this$1._handlers[event].forEach(function (handler) { return handler(data, message.lastEventId); }); 1209 | }; 1210 | 1211 | if (this._source) { 1212 | this._source.addEventListener(event, this._listeners[event]); 1213 | } 1214 | }; 1215 | 1216 | Object.defineProperties( SSEClient.prototype, prototypeAccessors ); 1217 | 1218 | function install(Vue, config) { 1219 | if (Vue.config && Vue.config.globalProperties) { 1220 | // Vue3 1221 | Vue.config.globalProperties.$sse = new SSEManager(config); 1222 | } else { 1223 | // Vue2 1224 | // eslint-disable-next-line no-param-reassign, no-multi-assign 1225 | Vue.$sse = Vue.prototype.$sse = new SSEManager(config); 1226 | } 1227 | 1228 | if (config && config.polyfill) { 1229 | Promise.resolve().then(function () { return eventsource$1; }); 1230 | } 1231 | 1232 | // This mixin allows components to specify that all clients that were 1233 | // created within it should be automatically disconnected (cleanup) 1234 | // when the component is destroyed. 1235 | Vue.mixin({ 1236 | beforeCreate: function beforeCreate() { 1237 | if (this.$options.sse && this.$options.sse.cleanup) { 1238 | // We instantiate an SSEManager for this specific instance 1239 | // in order to track it (see discussions in #13 for rationale). 1240 | this.$sse = new SSEManager(); 1241 | 1242 | // We also set $clients to an empty array, as opposed to null, 1243 | // so that beforeDestroy and create know to use it. 1244 | this.$sse.$clients = []; 1245 | } 1246 | }, 1247 | beforeDestroy: function beforeDestroy() { 1248 | if (this.$sse.$clients !== null) { 1249 | this.$sse.$clients.forEach(function (c) { return c.disconnect(); }); 1250 | this.$sse.$clients = []; 1251 | } 1252 | }, 1253 | }); 1254 | } 1255 | 1256 | var SSEManager = function SSEManager(config) { 1257 | this.$defaultConfig = Object.assign( 1258 | { 1259 | format: formatText, 1260 | sendCredentials: false, 1261 | }, 1262 | config 1263 | ); 1264 | 1265 | this.$clients = null; 1266 | }; 1267 | 1268 | SSEManager.prototype.create = function create (configOrURL) { 1269 | var config; 1270 | if (typeof configOrURL === 'object') { 1271 | config = configOrURL; 1272 | } else if (typeof configOrURL === 'string') { 1273 | config = { 1274 | url: configOrURL, 1275 | }; 1276 | } else { 1277 | config = {}; 1278 | } 1279 | 1280 | var client = new SSEClient(Object.assign({}, this.$defaultConfig, config)); 1281 | 1282 | // If $clients is not null, then it's array that we should push this 1283 | // client into for later cleanup in our mixin's beforeDestroy. 1284 | if (this.$clients !== null) { 1285 | this.$clients.push(client); 1286 | } 1287 | 1288 | return client; 1289 | }; 1290 | 1291 | var index = { 1292 | install: install, 1293 | }; 1294 | 1295 | export default index; 1296 | export { SSEManager, install }; 1297 | -------------------------------------------------------------------------------- /dist/vue-sse.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-sse v2.5.0 3 | * (c) 2021 James Churchard 4 | * @license MIT 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).VueSSE=t()}(this,(function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{}; 7 | /** @license 8 | * eventsource.js 9 | * Available under MIT License (MIT) 10 | * https://github.com/Yaffle/EventSource/ 11 | */ 12 | var t=function(e){var t={exports:{}};return e(t,t.exports),t.exports}((function(t,n){!function(e){var r=e.setTimeout,o=e.clearTimeout,i=e.XMLHttpRequest,s=e.XDomainRequest,a=e.ActiveXObject,l=e.EventSource,c=e.document,u=e.Promise,h=e.fetch,d=e.Response,f=e.TextDecoder,p=e.TextEncoder,y=e.AbortController;if("undefined"==typeof window||"readyState"in c||null!=c.body||(c.readyState="loading",window.addEventListener("load",(function(e){c.readyState="complete"}),!1)),null==i&&null!=a&&(i=function(){return new a("Microsoft.XMLHTTP")}),null==Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),Date.now||(Date.now=function(){return(new Date).getTime()}),null==y){var v=h;h=function(e,t){var n=t.signal;return v(e,{headers:t.headers,credentials:t.credentials,cache:t.cache}).then((function(e){var t=e.body.getReader();return n._reader=t,n._aborted&&n._reader.cancel(),{status:e.status,statusText:e.statusText,headers:e.headers,body:{getReader:function(){return t}}}}))},y=function(){this.signal={_reader:null,_aborted:!1},this.abort=function(){null!=this.signal._reader&&this.signal._reader.cancel(),this.signal._aborted=!0}}}function g(){this.bitsNeeded=0,this.codePoint=0}g.prototype.decode=function(e){function t(e,t,n){if(1===n)return e>=128>>t&&e<=2048>>t&&e<=57344>>t&&e<=65536>>t&&e<>6>15?3:t>31?2:1;if(12===e)return t>15?3:2;if(18===e)return 3;throw new Error}for(var r=65533,o="",i=this.bitsNeeded,s=this.codePoint,a=0;a191||!t(s<<6|63&l,i-6,n(i,s)))&&(i=0,s=r,o+=String.fromCharCode(s)),0===i?(l>=0&&l<=127?(i=0,s=l):l>=192&&l<=223?(i=6,s=31&l):l>=224&&l<=239?(i=12,s=15&l):l>=240&&l<=247?(i=18,s=7&l):(i=0,s=r),0===i||t(s,i,n(i,s))||(i=0,s=r)):(i-=6,s=s<<6|63&l),0===i&&(s<=65535?o+=String.fromCharCode(s):(o+=String.fromCharCode(55296+(s-65535-1>>10)),o+=String.fromCharCode(56320+(s-65535-1&1023))))}return this.bitsNeeded=i,this.codePoint=s,o};null!=f&&null!=p&&function(){try{return"test"===(new f).decode((new p).encode("test"),{stream:!0})}catch(e){console.debug("TextDecoder does not support streaming option. Using polyfill instead: "+e)}return!1}()||(f=g);var _=function(){};function w(e){this.withCredentials=!1,this.readyState=0,this.status=0,this.statusText="",this.responseText="",this.onprogress=_,this.onload=_,this.onerror=_,this.onreadystatechange=_,this._contentType="",this._xhr=e,this._sendTimeout=0,this._abort=_}function b(e){return e.replace(/[A-Z]/g,(function(e){return String.fromCharCode(e.charCodeAt(0)+32)}))}function m(e){for(var t=Object.create(null),n=e.split("\r\n"),r=0;r/test/**/*.spec.js'], 11 | testPathIgnorePatterns: ['/node_modules/'], 12 | transform: { 13 | '^.+\\.js$': '/node_modules/babel-jest', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-sse", 3 | "version": "2.5.2", 4 | "description": "A Vue plugin for using Server-Sent Events (EventSource)", 5 | "main": "dist/vue-sse.common.js", 6 | "exports": { 7 | ".": { 8 | "module": "./dist/vue-sse.esm.js", 9 | "require": "./dist/vue-sse.common.js", 10 | "import": "./dist/vue-sse.mjs" 11 | }, 12 | "./": "./" 13 | }, 14 | "module": "dist/vue-sse.esm.js", 15 | "typings": "types/index.d.ts", 16 | "unpkg": "dist/vue-sse.js", 17 | "jsdelivr": "dist/vue-sse.js", 18 | "sideEffects": [ 19 | "./src/sse-client.js" 20 | ], 21 | "files": [ 22 | "dist", 23 | "types/*.d.ts" 24 | ], 25 | "scripts": { 26 | "build": "rollup -c rollup.config.js", 27 | "format": "prettier --write \"src/**/*.js\"", 28 | "lint": "eslint src test", 29 | "postversion": "git push && git push --tags", 30 | "prepare": "npm run build", 31 | "prepublishOnly": "npm test && npm run lint", 32 | "preversion": "npm run lint", 33 | "test": "npm run lint && npm run test:unit", 34 | "test:unit": "jest", 35 | "test:types": "tsc -p types/test", 36 | "version": "npm run format && git add -A src" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/tserkov/vue-sse.git" 41 | }, 42 | "keywords": [ 43 | "vue", 44 | "sse", 45 | "eventsource", 46 | "server sent events" 47 | ], 48 | "author": "tserkov", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/tserkov/vue-sse/issues" 52 | }, 53 | "homepage": "https://github.com/tserkov/vue-sse", 54 | "dependencies": { 55 | "event-source-polyfill": "^1.0.22" 56 | }, 57 | "peerDependencies": { 58 | "vue": "^2.0.0 || ^3.0.0" 59 | }, 60 | "devDependencies": { 61 | "@babel/core": "^7.12.17", 62 | "@babel/preset-env": "^7.12.17", 63 | "@rollup/plugin-buble": "^0.21.3", 64 | "@rollup/plugin-commonjs": "^17.1.0", 65 | "@rollup/plugin-node-resolve": "^11.2.0", 66 | "@vue/test-utils": "^1.1.4", 67 | "babel-jest": "^26.6.3", 68 | "babel-loader": "^8.2.2", 69 | "eslint": "^7.20.0", 70 | "eslint-plugin-vue": "^7.6.0", 71 | "jest": "^26.6.3", 72 | "mocksse": "^1.0.2", 73 | "rollup": "^2.37.1", 74 | "rollup-plugin-terser": "^7.0.2", 75 | "typescript": "^4.1.5", 76 | "vue": "^2.6.12", 77 | "vue-loader": "^15.9.6" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from '@rollup/plugin-buble'; 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import { terser } from 'rollup-plugin-terser' 5 | import pkg from './package.json'; 6 | 7 | const banner = `/*! 8 | * vue-sse v${pkg.version} 9 | * (c) ${new Date().getFullYear()} James Churchard 10 | * @license MIT 11 | */` 12 | 13 | export default [ 14 | { 15 | input: 'src/index.js', 16 | output: { 17 | banner, 18 | file: 'dist/vue-sse.esm.browser.js', 19 | format: 'es', 20 | }, 21 | plugins: [ 22 | resolve(), 23 | commonjs(), 24 | ], 25 | inlineDynamicImports: true, 26 | }, 27 | { 28 | input: 'src/index.js', 29 | output: { 30 | banner, 31 | file: 'dist/vue-sse.esm.browser.min.js', 32 | format: 'es', 33 | }, 34 | plugins: [ 35 | resolve(), 36 | commonjs(), 37 | terser({ module: true }) 38 | ], 39 | inlineDynamicImports: true, 40 | }, 41 | { 42 | input: 'src/index.js', 43 | output: { 44 | banner, 45 | file: 'dist/vue-sse.esm.js', 46 | format: 'es', 47 | }, 48 | plugins: [ 49 | buble(), 50 | resolve(), 51 | commonjs(), 52 | ], 53 | inlineDynamicImports: true, 54 | }, 55 | { 56 | input: 'src/index.cjs.js', 57 | output: { 58 | banner, 59 | file: 'dist/vue-sse.js', 60 | format: 'umd', 61 | name: 'VueSSE', 62 | }, 63 | plugins: [ 64 | buble(), 65 | resolve(), 66 | commonjs(), 67 | ], 68 | inlineDynamicImports: true, 69 | }, 70 | { 71 | input: 'src/index.cjs.js', 72 | output: { 73 | banner, 74 | file: 'dist/vue-sse.min.js', 75 | format: 'umd', 76 | name: 'VueSSE', 77 | }, 78 | plugins: [ 79 | buble(), 80 | resolve(), 81 | commonjs(), 82 | terser({ module: false }) 83 | ], 84 | inlineDynamicImports: true, 85 | }, 86 | { 87 | input: 'src/index.cjs.js', 88 | output: { 89 | banner, 90 | file: 'dist/vue-sse.common.js', 91 | format: 'cjs', 92 | exports: 'default', 93 | }, 94 | plugins: [ 95 | buble(), 96 | resolve(), 97 | commonjs(), 98 | ], 99 | inlineDynamicImports: true, 100 | }, 101 | ]; 102 | -------------------------------------------------------------------------------- /src/index.cjs.js: -------------------------------------------------------------------------------- 1 | import SSEManager, { install } from './sse-manager'; 2 | 3 | export default { 4 | SSEManager, 5 | install, 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import SSEManager, { install } from './sse-manager'; 2 | 3 | export default { 4 | install, 5 | }; 6 | 7 | export { SSEManager, install }; 8 | -------------------------------------------------------------------------------- /src/sse-client.js: -------------------------------------------------------------------------------- 1 | import { EventSourcePolyfill } from 'event-source-polyfill'; 2 | 3 | export const formatText = (e) => e.data; 4 | 5 | export const formatJSON = (e) => JSON.parse(e.data); 6 | 7 | export default class SSEClient { 8 | constructor(config) { 9 | this._handlers = {}; 10 | this._listeners = {}; 11 | this._source = null; 12 | 13 | if (config.format) { 14 | if (typeof config.format === 'string') { 15 | if (config.format === 'plain') { 16 | this._format = formatText; 17 | } else if (config.format === 'json') { 18 | this._format = formatJSON; 19 | } else { 20 | this._format = formatText; 21 | } 22 | } else if (typeof config.format === 'function') { 23 | this._format = config.format; 24 | } else { 25 | this._format = formatText; 26 | } 27 | } else { 28 | this._format = formatText; 29 | } 30 | 31 | if (config.handlers) { 32 | for (const event in config.handlers) { 33 | this.on(event, config.handlers[event]); 34 | } 35 | } 36 | 37 | this.url = config.url; 38 | this.withCredentials = !!config.withCredentials; 39 | this.polyfillOptions = config.polyfillOptions || {}; 40 | this.forcePolyfill = !!config.forcePolyfill; 41 | } 42 | 43 | get source() { 44 | return this._source; 45 | } 46 | 47 | connect() { 48 | if (this.forcePolyfill) { 49 | this._source = new EventSourcePolyfill( 50 | this.url, 51 | Object.assign({}, this.polyfillOptions, { 52 | withCredentials: this.withCredentials, 53 | }), 54 | ); 55 | } else { 56 | this._source = new window.EventSource(this.url, { 57 | withCredentials: this.withCredentials, 58 | }); 59 | } 60 | 61 | return new Promise((resolve, reject) => { 62 | this._source.onopen = () => { 63 | // Add event listeners that were added before we connected 64 | for (let event in this._listeners) { 65 | this._source.addEventListener(event, this._listeners[event]); 66 | } 67 | 68 | this._source.onerror = null; 69 | 70 | resolve(this); 71 | }; 72 | 73 | this._source.onerror = reject; 74 | }); 75 | } 76 | 77 | disconnect() { 78 | if (this._source !== null) { 79 | this._source.close(); 80 | this._source = null; 81 | } 82 | } 83 | 84 | on(event, handler) { 85 | if (!event) { 86 | // Default "event-less" event 87 | event = 'message'; 88 | } 89 | 90 | if (!this._listeners[event]) { 91 | this._create(event); 92 | } 93 | 94 | this._handlers[event].push(handler); 95 | 96 | return this; 97 | } 98 | 99 | once(event, handler) { 100 | this.on(event, (e) => { 101 | this.off(event, handler); 102 | 103 | handler(e); 104 | }); 105 | 106 | return this; 107 | } 108 | 109 | off(event, handler) { 110 | if (!this._handlers[event]) { 111 | // no handlers registered for event 112 | return this; 113 | } 114 | 115 | const idx = this._handlers[event].indexOf(handler); 116 | if (idx === -1) { 117 | // handler not registered for event 118 | return this; 119 | } 120 | 121 | // remove handler from event 122 | this._handlers[event].splice(idx, 1); 123 | 124 | if (this._handlers[event].length === 0) { 125 | // remove listener since no handlers exist 126 | this._source.removeEventListener(event, this._listeners[event]); 127 | delete this._handlers[event]; 128 | delete this._listeners[event]; 129 | } 130 | 131 | return this; 132 | } 133 | 134 | _create(event) { 135 | this._handlers[event] = []; 136 | 137 | this._listeners[event] = (message) => { 138 | let data; 139 | 140 | try { 141 | data = this._format(message); 142 | } catch (err) { 143 | if (typeof this._source.onerror === 'function') { 144 | this._source.onerror(err); 145 | } 146 | return; 147 | } 148 | 149 | this._handlers[event].forEach((handler) => handler(data, message.lastEventId)); 150 | }; 151 | 152 | if (this._source) { 153 | this._source.addEventListener(event, this._listeners[event]); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/sse-manager.js: -------------------------------------------------------------------------------- 1 | import SSEClient, { formatText } from './sse-client'; 2 | 3 | export function install(Vue, config) { 4 | if (Vue.config && Vue.config.globalProperties) { 5 | // Vue3 6 | Vue.config.globalProperties.$sse = new SSEManager(config); 7 | } else { 8 | // Vue2 9 | // eslint-disable-next-line no-param-reassign, no-multi-assign 10 | Vue.$sse = Vue.prototype.$sse = new SSEManager(config); 11 | } 12 | 13 | if (config && config.polyfill) { 14 | import('event-source-polyfill'); 15 | } 16 | 17 | // This mixin allows components to specify that all clients that were 18 | // created within it should be automatically disconnected (cleanup) 19 | // when the component is destroyed. 20 | Vue.mixin({ 21 | beforeCreate() { 22 | if (this.$options.sse && this.$options.sse.cleanup) { 23 | // We instantiate an SSEManager for this specific instance 24 | // in order to track it (see discussions in #13 for rationale). 25 | this.$sse = new SSEManager(); 26 | 27 | // We also set $clients to an empty array, as opposed to null, 28 | // so that beforeDestroy and create know to use it. 29 | this.$sse.$clients = []; 30 | } 31 | }, 32 | beforeDestroy() { 33 | if (this.$sse.$clients !== null) { 34 | this.$sse.$clients.forEach((c) => c.disconnect()); 35 | this.$sse.$clients = []; 36 | } 37 | }, 38 | }); 39 | } 40 | 41 | export class SSEManager { 42 | constructor(config) { 43 | this.$defaultConfig = Object.assign( 44 | { 45 | format: formatText, 46 | sendCredentials: false, 47 | }, 48 | config, 49 | ); 50 | 51 | this.$clients = null; 52 | } 53 | 54 | create(configOrURL) { 55 | let config; 56 | if (typeof configOrURL === 'object') { 57 | config = configOrURL; 58 | } else if (typeof configOrURL === 'string') { 59 | config = { 60 | url: configOrURL, 61 | }; 62 | } else { 63 | config = {}; 64 | } 65 | 66 | const client = new SSEClient(Object.assign({}, this.$defaultConfig, config)); 67 | 68 | // If $clients is not null, then it's array that we should push this 69 | // client into for later cleanup in our mixin's beforeDestroy. 70 | if (this.$clients !== null) { 71 | this.$clients.push(client); 72 | } 73 | 74 | return client; 75 | } 76 | } 77 | 78 | export default SSEManager; 79 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import { EventSource } from 'mocksse'; 2 | 3 | Object.defineProperty(global, 'window', { 4 | value: { 5 | EventSource, 6 | navigator: { 7 | userAgent: 'jest', 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /test/sse-client.spec.js: -------------------------------------------------------------------------------- 1 | import { MockEvent } from 'mocksse'; 2 | import SSEClient from '../src/sse-client'; 3 | 4 | describe('SSEClient', () => { 5 | it('can receive eventless text messages', (done) => { 6 | new MockEvent({ 7 | url: '/eventless-text', 8 | responses: [{ 9 | type: 'message', 10 | data: 'a short message', 11 | }], 12 | }); 13 | 14 | const client = new SSEClient({ 15 | url: '/eventless-text', 16 | format: 'plain', 17 | handlers: { 18 | message: (msg) => { 19 | try { 20 | expect(msg).toEqual('a short message'); 21 | done(); 22 | } catch (err) { 23 | done(err); 24 | } finally { 25 | client.disconnect(); 26 | } 27 | }, 28 | }, 29 | }); 30 | 31 | client.connect(); 32 | }); 33 | 34 | it('can receive eventless json messages', (done) => { 35 | new MockEvent({ 36 | url: '/eventless-json', 37 | responses: [{ 38 | type: 'message', 39 | data: '{"pi":3.14}', 40 | }], 41 | }); 42 | 43 | const client = new SSEClient({ 44 | url: '/eventless-json', 45 | format: 'json', 46 | handlers: { 47 | message: (msg) => { 48 | try { 49 | expect(msg).toStrictEqual({ pi: 3.14 }); 50 | done(); 51 | } catch (err) { 52 | done(err); 53 | } finally { 54 | client.disconnect(); 55 | } 56 | }, 57 | }, 58 | }); 59 | 60 | client.connect(); 61 | }); 62 | 63 | it('can receive custom event messages', (done) => { 64 | new MockEvent({ 65 | url: '/custom-event', 66 | responses: [{ 67 | type: 'ping', 68 | data: 'ok!', 69 | }], 70 | }); 71 | 72 | const client = new SSEClient({ 73 | url: '/custom-event', 74 | handlers: { 75 | ping: (msg) => { 76 | try { 77 | expect(msg).toStrictEqual('ok!'); 78 | done(); 79 | } catch (err) { 80 | done(err); 81 | } finally { 82 | client.disconnect(); 83 | } 84 | }, 85 | }, 86 | }); 87 | 88 | client.connect(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/sse-manager.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils'; 2 | import SSEManager from '../src/sse-manager'; 3 | import VueSSE from '../src/index'; 4 | 5 | describe('SSEManager', () => { 6 | it('creates a client that inherits from manager config', () => { 7 | const $sse = new SSEManager({ 8 | url: 'foo.local', 9 | withCredentials: true, 10 | }); 11 | 12 | const client = $sse.create({}); 13 | 14 | expect(client.url).toEqual('foo.local'); 15 | expect(client.withCredentials).toEqual(true); 16 | }); 17 | 18 | it('creates a client that overrides manager config', () => { 19 | const $sse = new SSEManager({ 20 | url: 'foo.local', 21 | withCredentials: true, 22 | }); 23 | 24 | const client = $sse.create({ 25 | url: 'bar.local', 26 | withCredentials: false, 27 | }); 28 | 29 | expect(client.url).toEqual('bar.local'); 30 | expect(client.withCredentials).toEqual(false); 31 | }); 32 | 33 | it('creates a client from url string not config object', () => { 34 | const $sse = new SSEManager(); 35 | 36 | const client = $sse.create('foo.local'); 37 | 38 | expect(client.url).toEqual('foo.local'); 39 | }); 40 | 41 | it('creates a client and cleans up', () => { 42 | const localVue = createLocalVue() 43 | localVue.use(VueSSE); 44 | 45 | const wrapper = mount({ 46 | template: '
', 47 | sse: { 48 | cleanup: true, 49 | }, 50 | mounted() { 51 | this.$sse.create('bar.local'); 52 | }, 53 | }, { localVue }); 54 | 55 | wrapper.destroy(); 56 | 57 | expect(wrapper.vm.$sse.$clients).not.toBe(null); 58 | expect(wrapper.vm.$sse.$clients).toHaveLength(0); 59 | }); 60 | 61 | it('creates a client and does not clean up', () => { 62 | const localVue = createLocalVue() 63 | localVue.use(VueSSE); 64 | 65 | const wrapper = mount({ 66 | template: '
', 67 | sse: { 68 | cleanup: false, 69 | }, 70 | mounted() { 71 | this.$sse.create('bar.local'); 72 | }, 73 | }, { localVue }); 74 | 75 | wrapper.destroy(); 76 | 77 | expect(wrapper.vm.$sse.$clients).toBe(null); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import './vue'; 2 | import Vue from 'vue'; 3 | 4 | export type MessageFormatter = (event: MessageEvent) => T; 5 | 6 | export type MessageHandler = (data: any, lastEventId: string) => void; 7 | 8 | export interface SSEConfig { 9 | format?: 'plain' | 'json' | MessageFormatter; 10 | handlers?: Partial>; 11 | polyfill?: boolean; 12 | forcePolyfill?: boolean; 13 | polyfillOptions?: object; 14 | url?: string; 15 | withCredentials?: boolean; 16 | } 17 | 18 | export interface SSEComponentOptions { 19 | cleanup?: boolean; 20 | } 21 | 22 | export declare class SSEManager { 23 | $defaultConfig: SSEConfig; 24 | $clients: SSEClient[] | null; 25 | 26 | constructor(config?: SSEConfig); 27 | create(configOrURL?: SSEConfig | string): SSEClient; 28 | } 29 | 30 | export declare class SSEClient { 31 | url: string; 32 | withCredentials: boolean; 33 | 34 | _format: MessageFormatter; 35 | _handlers: Partial>; 36 | _listeners: Partial>; 37 | _source: EventSource; 38 | 39 | constructor(config?: SSEConfig); 40 | connect(): Promise; 41 | disconnect(): void; 42 | on(event: string, handler: MessageHandler): SSEClient; 43 | once(event: string, handler: MessageHandler): SSEClient; 44 | off(event: string, handler: MessageHandler): SSEClient; 45 | 46 | get source(): EventSource; 47 | } 48 | 49 | export declare function install(vue: typeof Vue, config?: SSEConfig): void; 50 | 51 | declare const _default: { 52 | SSEClient: typeof SSEClient; 53 | SSEManager: typeof SSEManager; 54 | install: typeof install; 55 | } 56 | export default _default; 57 | -------------------------------------------------------------------------------- /types/vue.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { SSEComponentOptions, SSEManager } from './index'; 3 | 4 | declare module 'vue/types/vue' { 5 | interface VueConstructor { 6 | readonly $sse: SSEManager; 7 | } 8 | 9 | interface Vue { 10 | $sse: SSEManager; 11 | } 12 | } 13 | 14 | declare module 'vue/types/options' { 15 | interface ComponentOptions { 16 | sse?: SSEComponentOptions; 17 | } 18 | } 19 | --------------------------------------------------------------------------------