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

Commit cd4a1a7

Browse files
committed
Update EventSource
1 parent 89fc402 commit cd4a1a7

3 files changed

Lines changed: 126 additions & 87 deletions

File tree

iOS/RNEventSource/EventSource.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ typedef void (^EventSourceEventHandler)(Event *event);
7979
/// @param handler The handler for the Open event.
8080
- (void)onOpen:(EventSourceEventHandler)handler;
8181

82+
- (void)onReadyStateChanged:(EventSourceEventHandler)handler;
83+
8284
/// Registers an event handler for a named event.
8385
///
8486
/// @param eventName The name of the event you registered.
@@ -95,3 +97,4 @@ typedef void (^EventSourceEventHandler)(Event *event);
9597
extern NSString *const MessageEvent;
9698
extern NSString *const ErrorEvent;
9799
extern NSString *const OpenEvent;
100+
extern NSString *const ReadyStateEvent;

iOS/RNEventSource/EventSource.m

Lines changed: 122 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
static CGFloat const ES_RETRY_INTERVAL = 1.0;
1313
static CGFloat const ES_DEFAULT_TIMEOUT = 300.0;
1414

15-
static NSString *const ESKeyValueDelimiter = @": ";
15+
static NSString *const ESKeyValueDelimiter = @":";
1616
static NSString *const ESEventSeparatorLFLF = @"\n\n";
1717
static NSString *const ESEventSeparatorCRCR = @"\r\r";
1818
static NSString *const ESEventSeparatorCRLFCRLF = @"\r\n\r\n";
@@ -25,6 +25,8 @@
2525

2626
@interface EventSource () <NSURLConnectionDelegate, NSURLConnectionDataDelegate> {
2727
BOOL wasClosed;
28+
dispatch_queue_t messageQueue;
29+
dispatch_queue_t connectionQueue;
2830
}
2931

3032
@property (nonatomic, strong) NSURL *eventURL;
@@ -34,39 +36,43 @@ @interface EventSource () <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
3436
@property (nonatomic, assign) NSTimeInterval retryInterval;
3537
@property (nonatomic, strong) id lastEventID;
3638

37-
- (void)open;
39+
- (void)_open;
40+
- (void)_dispatchEvent:(Event *)e;
3841

3942
@end
4043

4144
@implementation EventSource
4245

43-
+ (id)eventSourceWithURL:(NSURL *)URL
46+
+ (instancetype)eventSourceWithURL:(NSURL *)URL
4447
{
4548
return [[EventSource alloc] initWithURL:URL];
4649
}
4750

48-
+ (id)eventSourceWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval
51+
+ (instancetype)eventSourceWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval
4952
{
5053
return [[EventSource alloc] initWithURL:URL timeoutInterval:timeoutInterval];
5154
}
5255

53-
- (id)initWithURL:(NSURL *)URL
56+
- (instancetype)initWithURL:(NSURL *)URL
5457
{
5558
return [self initWithURL:URL timeoutInterval:ES_DEFAULT_TIMEOUT];
5659
}
5760

58-
- (id)initWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval
61+
- (instancetype)initWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval
5962
{
6063
self = [super init];
6164
if (self) {
6265
_listeners = [NSMutableDictionary dictionary];
6366
_eventURL = URL;
6467
_timeoutInterval = timeoutInterval;
6568
_retryInterval = ES_RETRY_INTERVAL;
66-
69+
70+
messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL);
71+
connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
72+
6773
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC));
68-
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
69-
[self open];
74+
dispatch_after(popTime, connectionQueue, ^(void){
75+
[self _open];
7076
});
7177
}
7278
return self;
@@ -96,14 +102,9 @@ - (void)onOpen:(EventSourceEventHandler)handler
96102
[self addEventListener:OpenEvent handler:handler];
97103
}
98104

99-
- (void)open
105+
- (void)onReadyStateChanged:(EventSourceEventHandler)handler
100106
{
101-
wasClosed = NO;
102-
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.timeoutInterval];
103-
if (self.lastEventID) {
104-
[request setValue:self.lastEventID forHTTPHeaderField:@"Last-Event-ID"];
105-
}
106-
self.eventSource = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
107+
[self addEventListener:ReadyStateEvent handler:handler];
107108
}
108109

109110
- (void)close
@@ -122,94 +123,91 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon
122123
Event *e = [Event new];
123124
e.readyState = kEventStateOpen;
124125

125-
NSArray *openHandlers = self.listeners[OpenEvent];
126-
for (EventSourceEventHandler handler in openHandlers) {
127-
dispatch_async(dispatch_get_main_queue(), ^{
128-
handler(e);
129-
});
130-
}
126+
127+
[self _dispatchEvent:e type:ReadyStateEvent];
128+
[self _dispatchEvent:e type:OpenEvent];
131129
}
132130
}
133131

134132
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
135133
{
134+
self.eventSource = nil;
135+
136+
if (wasClosed) {
137+
return;
138+
}
139+
136140
Event *e = [Event new];
137141
e.readyState = kEventStateClosed;
138142
e.error = error;
139-
140-
NSArray *errorHandlers = self.listeners[ErrorEvent];
141-
for (EventSourceEventHandler handler in errorHandlers) {
142-
dispatch_async(dispatch_get_main_queue(), ^{
143-
handler(e);
144-
});
145-
}
146-
147-
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.retryInterval * NSEC_PER_SEC));
148-
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
149-
[self open];
143+
144+
[self _dispatchEvent:e type:ReadyStateEvent];
145+
[self _dispatchEvent:e type:ErrorEvent];
146+
147+
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC));
148+
dispatch_after(popTime, connectionQueue, ^(void){
149+
[self _open];
150150
});
151151
}
152152

153153
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
154154
{
155-
__block NSString *eventString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
156-
157-
if ([eventString hasSuffix:ESEventSeparatorLFLF] ||
158-
[eventString hasSuffix:ESEventSeparatorCRCR] ||
159-
[eventString hasSuffix:ESEventSeparatorCRLFCRLF]) {
160-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
161-
eventString = [eventString stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
162-
NSMutableArray *components = [[eventString componentsSeparatedByString:ESEventKeyValuePairSeparator] mutableCopy];
163-
164-
Event *e = [Event new];
165-
e.readyState = kEventStateOpen;
166-
167-
for (NSString *component in components) {
168-
if (component.length == 0) {
169-
continue;
170-
}
171-
172-
NSInteger index = [component rangeOfString:ESKeyValueDelimiter].location;
173-
if (index == NSNotFound || index == (component.length - 2)) {
174-
continue;
175-
}
176-
177-
NSString *key = [component substringToIndex:index];
178-
NSString *value = [component substringFromIndex:index + ESKeyValueDelimiter.length];
155+
NSString *eventString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
156+
NSArray *lines = [eventString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
157+
158+
Event *event = [Event new];
159+
event.readyState = kEventStateOpen;
160+
161+
for (NSString *line in lines) {
162+
if ([line hasPrefix:ESKeyValueDelimiter]) {
163+
continue;
164+
}
165+
166+
if (!line || line.length == 0) {
167+
if (event.data != nil) {
168+
dispatch_async(messageQueue, ^{
169+
[self _dispatchEvent:event];
170+
});
179171

180-
if ([key isEqualToString:ESEventIDKey]) {
181-
e.id = value;
182-
self.lastEventID = e.id;
183-
} else if ([key isEqualToString:ESEventEventKey]) {
184-
e.event = value;
172+
event = [Event new];
173+
event.readyState = kEventStateOpen;
174+
}
175+
continue;
176+
}
177+
178+
@autoreleasepool {
179+
NSScanner *scanner = [NSScanner scannerWithString:line];
180+
scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet];
181+
182+
NSString *key, *value;
183+
[scanner scanUpToString:ESKeyValueDelimiter intoString:&key];
184+
[scanner scanString:ESKeyValueDelimiter intoString:nil];
185+
[scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&value];
186+
187+
if (key && value) {
188+
if ([key isEqualToString:ESEventEventKey]) {
189+
event.event = value;
185190
} else if ([key isEqualToString:ESEventDataKey]) {
186-
e.data = value;
191+
if (event.data != nil) {
192+
event.data = [event.data stringByAppendingFormat:@"\n%@", value];
193+
} else {
194+
event.data = value;
195+
}
196+
} else if ([key isEqualToString:ESEventIDKey]) {
197+
event.id = value;
198+
self.lastEventID = event.id;
187199
} else if ([key isEqualToString:ESEventRetryKey]) {
188200
self.retryInterval = [value doubleValue];
189201
}
190202
}
191-
192-
NSArray *messageHandlers = self.listeners[MessageEvent];
193-
for (EventSourceEventHandler handler in messageHandlers) {
194-
dispatch_async(dispatch_get_main_queue(), ^{
195-
handler(e);
196-
});
197-
}
198-
199-
if (e.event != nil) {
200-
NSArray *namedEventhandlers = self.listeners[e.event];
201-
for (EventSourceEventHandler handler in namedEventhandlers) {
202-
dispatch_async(dispatch_get_main_queue(), ^{
203-
handler(e);
204-
});
205-
}
206-
}
207-
});
203+
}
208204
}
209205
}
210206

211207
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
212208
{
209+
self.eventSource = nil;
210+
213211
if (wasClosed) {
214212
return;
215213
}
@@ -219,15 +217,52 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection
219217
e.error = [NSError errorWithDomain:@""
220218
code:e.readyState
221219
userInfo:@{ NSLocalizedDescriptionKey: @"Connection with the event source was closed." }];
222-
223-
NSArray *errorHandlers = self.listeners[ErrorEvent];
220+
221+
[self _dispatchEvent:e type:ReadyStateEvent];
222+
[self _dispatchEvent:e type:ErrorEvent];
223+
224+
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC));
225+
dispatch_after(popTime, connectionQueue, ^(void){
226+
[self _open];
227+
});
228+
}
229+
230+
- (void)_open
231+
{
232+
wasClosed = NO;
233+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.timeoutInterval];
234+
if (self.lastEventID) {
235+
[request setValue:self.lastEventID forHTTPHeaderField:@"Last-Event-ID"];
236+
}
237+
self.eventSource = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
238+
239+
Event *e = [Event new];
240+
e.readyState = kEventStateConnecting;
241+
242+
[self _dispatchEvent:e type:ReadyStateEvent];
243+
244+
if (![NSThread isMainThread]) {
245+
CFRunLoopRun();
246+
}
247+
}
248+
249+
- (void)_dispatchEvent:(Event *)event type:(NSString * const)type
250+
{
251+
NSArray *errorHandlers = self.listeners[type];
224252
for (EventSourceEventHandler handler in errorHandlers) {
225-
dispatch_async(dispatch_get_main_queue(), ^{
226-
handler(e);
253+
dispatch_async(connectionQueue, ^{
254+
handler(event);
227255
});
228256
}
229-
230-
[self open];
257+
}
258+
259+
- (void)_dispatchEvent:(Event *)event
260+
{
261+
[self _dispatchEvent:event type:MessageEvent];
262+
263+
if (event.event != nil) {
264+
[self _dispatchEvent:event type:event.event];
265+
}
231266
}
232267

233268
@end
@@ -264,3 +299,4 @@ - (NSString *)description
264299
NSString *const MessageEvent = @"message";
265300
NSString *const ErrorEvent = @"error";
266301
NSString *const OpenEvent = @"open";
302+
NSString *const ReadyStateEvent = @"readyState";

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-event-source",
3-
"version": "0.0.4",
3+
"version": "0.1.0",
44
"description": "A react-native component for EventSource: Server-Sent Events for iOS",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)