Skip to content
This repository was archived by the owner on Apr 15, 2023. It is now read-only.

Commit 2c22c81

Browse files
committed
Pure JS EventSource implementation
Squashed commit of the following: commit 6ae578f Author: Jordan Byron <jordan.byron@gmail.com> Date: Fri Oct 28 08:52:46 2016 -0400 Move readyState CONNECTING to onerror function commit 3ae6b94 Author: Jordan Byron <jordan.byron@gmail.com> Date: Fri Sep 30 10:58:07 2016 -0400 Version bump commit 11f6521 Author: Jordan Byron <jordan.byron@gmail.com> Date: Fri Sep 30 10:56:14 2016 -0400 Send connection errors back to RN apps commit 174cefd Author: Jordan Byron <jordan.byron@gmail.com> Date: Tue Sep 20 19:05:22 2016 -0400 Add full example of RNEventSource commit 9c8d44b Author: Jordan Byron <jordan.byron@gmail.com> Date: Tue Sep 20 19:05:13 2016 -0400 Add copyright to EvenSource file commit e3600ca Author: Jordan Byron <jordan.byron@gmail.com> Date: Tue Sep 20 18:51:06 2016 -0400 Fix syntax error commit c08082f Author: Jordan Byron <jordan.byron@gmail.com> Date: Sun Sep 18 07:56:29 2016 -0400 Update README commit 72d6647 Author: Jordan Byron <jordan.byron@gmail.com> Date: Sun Sep 18 07:47:10 2016 -0400 Full iOS (and Android?) support with JS event-source
1 parent 9304011 commit 2c22c81

10 files changed

Lines changed: 259 additions & 946 deletions

File tree

EventSource.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// EventSource.js
2+
// Original implementation from
3+
// https://github.com/remy/polyfills/blob/master/EventSource.js
4+
//
5+
// Copyright (c) 2010 Remy Sharp, http://remysharp.com
6+
7+
var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
8+
9+
var EventSource = function (url) {
10+
var eventsource = this,
11+
interval = 500, // polling interval
12+
lastEventId = null,
13+
cache = '',
14+
eventType;
15+
16+
if (!url || typeof url != 'string') {
17+
throw new SyntaxError('Not enough arguments');
18+
}
19+
20+
this.URL = url;
21+
this.readyState = this.CONNECTING;
22+
this._pollTimer = null;
23+
this._xhr = null;
24+
25+
function pollAgain(interval) {
26+
eventsource._pollTimer = setTimeout(function () {
27+
poll.call(eventsource);
28+
}, interval);
29+
}
30+
31+
function poll() {
32+
try { // force hiding of the error message... insane?
33+
if (eventsource.readyState == eventsource.CLOSED) return;
34+
35+
// NOTE: IE7 and upwards support
36+
var xhr = new XMLHttpRequest();
37+
xhr.open('GET', eventsource.URL, true);
38+
xhr.setRequestHeader('Accept', 'text/event-stream');
39+
xhr.setRequestHeader('Cache-Control', 'no-cache');
40+
// we must make use of this on the server side if we're working with Android - because they don't trigger
41+
// readychange until the server connection is closed
42+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
43+
44+
if (lastEventId != null) xhr.setRequestHeader('Last-Event-ID', lastEventId);
45+
cache = '';
46+
47+
xhr.timeout = 50000;
48+
xhr.onreadystatechange = function () {
49+
if (this.readyState == 3 || (this.readyState == 4 && this.status == 200)) {
50+
// on success
51+
if (eventsource.readyState == eventsource.CONNECTING) {
52+
eventsource.readyState = eventsource.OPEN;
53+
eventsource.dispatchEvent('open', { type: 'open' });
54+
}
55+
56+
var responseText = '';
57+
try {
58+
responseText = this.responseText || '';
59+
} catch (e) {}
60+
61+
// process this.responseText
62+
var parts = responseText.substr(cache.length).split("\n"),
63+
data = [],
64+
i = 0,
65+
line = '';
66+
67+
cache = responseText;
68+
69+
// TODO handle 'event' (for buffer name), retry
70+
for (; i < parts.length; i++) {
71+
line = parts[i].replace(reTrim, '');
72+
if (line.indexOf('event') == 0) {
73+
eventType = line.replace(/event:?\s*/, '');
74+
} else if (line.indexOf('retry') == 0) {
75+
retry = parseInt(line.replace(/retry:?\s*/, ''));
76+
if(!isNaN(retry)) { interval = retry; }
77+
} else if (line.indexOf('data') == 0) {
78+
data.push(line.replace(/data:?\s*/, ''));
79+
} else if (line.indexOf('id:') == 0) {
80+
lastEventId = line.replace(/id:?\s*/, '');
81+
} else if (line.indexOf('id') == 0) { // this resets the id
82+
lastEventId = null;
83+
} else if (line == '') {
84+
if (data.length) {
85+
var event = new MessageEvent(data.join('\n'), eventsource.url, lastEventId);
86+
eventsource.dispatchEvent(eventType || 'message', event);
87+
data = [];
88+
eventType = undefined;
89+
}
90+
}
91+
}
92+
93+
if (this.readyState == 4) pollAgain(interval);
94+
95+
// don't need to poll again, because we're long-loading
96+
} else if (eventsource.readyState !== eventsource.CLOSED) {
97+
if (this.readyState == 4) { // and some other status
98+
pollAgain(interval);
99+
} else if (this.readyState == 0) { // likely aborted
100+
pollAgain(interval);
101+
}
102+
}
103+
};
104+
105+
xhr.onerror = function(e) {
106+
// dispatch error
107+
eventsource.readyState = eventsource.CONNECTING;
108+
109+
eventsource.dispatchEvent('error',
110+
{ type: 'error', message: this.responseText });
111+
}
112+
113+
xhr.send();
114+
115+
setTimeout(function () {
116+
if (true || xhr.readyState == 3) xhr.abort();
117+
}, xhr.timeout);
118+
119+
eventsource._xhr = xhr;
120+
121+
} catch (e) { // in an attempt to silence the errors
122+
eventsource.dispatchEvent('error', { type: 'error', data: e.message }); // ???
123+
}
124+
};
125+
126+
poll(); // init now
127+
};
128+
129+
EventSource.prototype = {
130+
close: function () {
131+
// closes the connection - disabling the polling
132+
this.readyState = this.CLOSED;
133+
clearInterval(this._pollTimer);
134+
this._xhr.abort();
135+
},
136+
CONNECTING: 0,
137+
OPEN: 1,
138+
CLOSED: 2,
139+
dispatchEvent: function (type, event) {
140+
var handlers = this['_' + type + 'Handlers'];
141+
if (handlers) {
142+
for (var i = 0; i < handlers.length; i++) {
143+
handlers[i].call(this, event);
144+
}
145+
}
146+
147+
if (this['on' + type]) {
148+
this['on' + type].call(this, event);
149+
}
150+
},
151+
addEventListener: function (type, handler) {
152+
if (!this['_' + type + 'Handlers']) {
153+
this['_' + type + 'Handlers'] = [];
154+
}
155+
156+
this['_' + type + 'Handlers'].push(handler);
157+
},
158+
removeEventListener: function (type, handler) {
159+
var handlers = this['_' + type + 'Handlers'];
160+
if (!handlers) {
161+
return;
162+
}
163+
for (var i = handlers.length - 1; i >= 0; --i) {
164+
if (handlers[i] === handler) {
165+
handlers.splice(i, 1);
166+
break;
167+
}
168+
}
169+
},
170+
onerror: null,
171+
onmessage: null,
172+
onopen: null,
173+
readyState: 0,
174+
URL: ''
175+
};
176+
177+
var MessageEvent = function (data, origin, lastEventId) {
178+
this.data = data;
179+
this.origin = origin;
180+
this.lastEventId = lastEventId || '';
181+
};
182+
183+
MessageEvent.prototype = {
184+
data: null,
185+
type: 'message',
186+
lastEventId: '',
187+
origin: ''
188+
};
189+
190+
export default EventSource;

README.md

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,88 +16,58 @@ Run the following command in your project's directory to grab the latest publish
1616
$ npm install react-native-event-source --save
1717
```
1818

19-
### Linking the Library
20-
21-
In order to use event source you must first link the library to your project. There's excellent documentation on how to do this in the [React Native Docs](http://facebook.github.io/react-native/docs/linking-libraries-ios.html#content). Make sure you do all steps including #3.
22-
2319
### Using in your project
2420

25-
First, you'll need to make sure `DeviceEventEmitter` is added to the list of
26-
requires for React.
27-
28-
```js
29-
var React = require('react-native');
30-
var {
31-
//....things you need plus....
32-
DeviceEventEmitter
33-
} = React;
34-
```
35-
36-
Next grab `RNEventSource` and assign it to a variable.
37-
3821
```js
39-
var EventSource = require('NativeModules').RNEventSource;
22+
import RNEventSource from 'react-native-event-source'
4023
```
4124

4225
Now you're ready to connect to your SSE endpoint and start streaming updates!
4326
:godmode:
44-
Use `DeviceEventEmitter` to listen for `EventSourceMessage` messages. You can
45-
also subscribe to `EventSourceConnected` and `EventSourceError` to be notified
46-
when your connection is established or encounters any errors.
4727

4828
```js
49-
var subscription = DeviceEventEmitter.addListener(
50-
'EventSourceMessage', function(message) {
51-
console.log(message.event);
52-
console.log(message.data);
53-
});
29+
const eventSource = new RNEventSource('https://my-sse.com/stream');
5430

55-
EventSource.connectWithURL("http://your-sse-url.com/stream");
31+
eventSource.addEventListener('message', (event) {
32+
console.log(event.type); // message
33+
console.log(event.data);
34+
});
5635
```
5736

5837
Here is a full example that subscribes to a SSE stream and writes the results to `console.log`
5938

6039
```js
61-
var React = require('react-native');
62-
var {
63-
AppRegistry,
64-
Text,
65-
View,
66-
DeviceEventEmitter,
67-
} = React;
68-
69-
var EventSource = require('NativeModules').RNEventSource,
70-
subscription;
71-
72-
var MyFancyApp = React.createClass({
73-
getDefaultProps: function() {
74-
return {
75-
url: "http://your-sse-url.com/stream"
76-
};
77-
},
78-
componentDidMount: function() {
79-
subscription = DeviceEventEmitter.addListener(
80-
'EventSourceMessage', function(message) {
81-
console.log(message.event);
82-
});
83-
84-
EventSource.connectWithURL(this.props.url);
85-
},
86-
componentDidUmnount: function() {
87-
EventSource.close();
88-
subscription.remove();
89-
},
90-
render: function() {
91-
return (<View><Text>SSE in React!</Text></View>)
40+
import React, { Component } from 'react';
41+
import { View, Text } from 'react-native';
42+
43+
import RNEventSource from 'react-native-event-source';
44+
45+
class MyApp extends Component {
46+
componentDidMount() {
47+
this.eventSource = new EventSource('https://sse.com/stream');
48+
49+
// Grab all events with the type of 'message'
50+
this.eventSource.addEventListener('message', (data) => {
51+
console.log(data.type); // message
52+
console.log(data.data);
53+
});
9254
}
93-
});
55+
componentDidUmnount() {
56+
this.eventSource.removeAllListeners();
57+
this.eventSource.close();
58+
}
59+
render() {
60+
return (
61+
<View>
62+
<Text>Streaming!</Text>
63+
</View>
64+
)
65+
}
66+
}
9467
```
9568

9669
## License
9770

98-
See [EventSource](https://github.com/neilco/EventSource/blob/master/LICENSE.txt)
99-
for additional license details.
100-
10171
Copyright (c) 2015 Jordan Byron (http://github.com/jordanbyron/)
10272

10373
Permission is hereby granted, free of charge, to any person obtaining a copy

0 commit comments

Comments
 (0)