Skip to content

Commit 3d63054

Browse files
authored
Comply with the latest EventSource specification (#31)
1 parent 5300249 commit 3d63054

File tree

7 files changed

+321
-173
lines changed

7 files changed

+321
-173
lines changed

Source/EventParser.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ class EventParser {
88
static let idLabel: Substring = "id"
99
static let eventLabel: Substring = "event"
1010
static let retryLabel: Substring = "retry"
11-
static let defaultEventType = "message"
1211
}
1312

1413
private let handler: EventHandler
1514
private let connectionHandler: ConnectionHandler
1615

1716
private var data: String = ""
1817
private var lastEventId: String?
19-
private var eventType: String = Constants.defaultEventType
18+
private var eventType: String = ""
2019

2120
init(handler: EventHandler, connectionHandler: ConnectionHandler) {
2221
self.handler = handler
@@ -36,6 +35,11 @@ class EventParser {
3635
}
3736
}
3837

38+
func reset() {
39+
data = ""
40+
eventType = ""
41+
}
42+
3943
private func dropLeadingSpace(str: Substring) -> Substring {
4044
if str.first == " " {
4145
return str[str.index(after: str.startIndex)...]
@@ -46,12 +50,14 @@ class EventParser {
4650
private func processField(field: Substring, value: Substring) {
4751
switch field {
4852
case Constants.dataLabel:
49-
if !data.isEmpty {
50-
data.append(contentsOf: "\n")
51-
}
5253
data.append(contentsOf: value)
54+
data.append(contentsOf: "\n")
5355
case Constants.idLabel:
54-
lastEventId = String(value)
56+
// See https://github.com/whatwg/html/issues/689 for reasoning on not setting lastEventId if the value
57+
// contains a null code point.
58+
if !value.contains("\u{0000}") {
59+
lastEventId = String(value)
60+
}
5561
case Constants.eventLabel:
5662
eventType = String(value)
5763
case Constants.retryLabel:
@@ -64,15 +70,20 @@ class EventParser {
6470
}
6571

6672
private func dispatchEvent() {
67-
guard !data.isEmpty
68-
else { return }
69-
let messageEvent = MessageEvent(data: data, lastEventId: lastEventId)
7073
if let lastEventId = lastEventId {
7174
connectionHandler.setLastEventId(lastEventId)
7275
}
73-
handler.onMessage(eventType: eventType, messageEvent: messageEvent)
76+
guard !data.isEmpty
77+
else {
78+
eventType = ""
79+
return
80+
}
81+
// remove the last LF
82+
_ = data.popLast()
83+
let messageEvent = MessageEvent(data: data, lastEventId: lastEventId)
84+
handler.onMessage(eventType: eventType.isEmpty ? "message" : eventType, messageEvent: messageEvent)
7485
data = ""
75-
eventType = Constants.defaultEventType
86+
eventType = ""
7687
}
7788
}
7889

Source/LDSwiftEventSource.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,8 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
242242
public func urlSession(_ session: URLSession,
243243
task: URLSessionTask,
244244
didCompleteWithError error: Error?) {
245-
utf8LineParser.closeAndReset().forEach(eventParser.parse)
246-
// Send additional empty line to force a last dispatch
247-
eventParser.parse(line: "")
245+
utf8LineParser.closeAndReset()
246+
eventParser.reset()
248247

249248
if let error = error {
250249
if readyState != .shutdown && errorHandlerAction != .shutdown {

Source/Logs.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,23 @@ class Logs {
88
enum Level {
99
case debug, info, warn, error
1010

11-
#if !os(Linux)
11+
#if !os(Linux)
1212
private static let osLogTypes = [ Level.debug: OSLogType.debug,
1313
Level.info: OSLogType.info,
1414
Level.warn: OSLogType.default,
1515
Level.error: OSLogType.error]
1616
var osLogType: OSLogType { Level.osLogTypes[self]! }
17-
#endif
17+
#endif
1818
}
1919

20-
#if !os(Linux)
20+
#if !os(Linux)
2121
private let logger: OSLog = OSLog(subsystem: "com.launchdarkly.swift-eventsource", category: "LDEventSource")
22-
#endif
2322

2423
func log(_ level: Level, _ staticMsg: StaticString, _ args: CVarArg...) {
25-
#if !os(Linux)
2624
os_log(staticMsg, log: logger, type: level.osLogType, args)
27-
#endif
2825
}
26+
#else
27+
// We use Any over CVarArg here, because on Linux prior to Swift 5.4 String does not conform to CVarArg
28+
func log(_ level: Level, _ staticMsg: StaticString, _ args: Any...) { }
29+
#endif
2930
}

Source/UTF8LineParser.swift

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
import Foundation
22

3-
class DataIter: IteratorProtocol {
4-
let data: Data
5-
var position: Data.Index
3+
struct DataIter: IteratorProtocol {
4+
var data: Data
5+
var position: Data.Index { data.startIndex }
66

7-
init(_ data: Data) {
8-
self.data = data
9-
self.position = data.startIndex
10-
}
11-
12-
func next() -> UInt8? {
13-
guard position != data.endIndex
14-
else { return nil }
15-
16-
let res = data[position]
17-
position = data.index(after: position)
18-
return res
7+
mutating func next() -> UInt8? {
8+
data.popFirst()
199
}
2010
}
2111

@@ -31,25 +21,22 @@ class UTF8LineParser {
3121

3222
func append(_ body: Data) -> [String] {
3323
let data = remainder + body
34-
var dataIter = DataIter(data)
24+
var dataIter = DataIter(data: data)
3525
var remainderPos = data.endIndex
3626
var lines: [String] = []
3727

3828
Decode: while true {
3929
switch utf8Parser.parseScalar(from: &dataIter) {
4030
case .valid(let scalarResult):
4131
let scalar = Unicode.UTF8.decode(scalarResult)
42-
if seenCr {
43-
lines.append(currentString)
44-
currentString = ""
32+
33+
if seenCr && scalar == lf {
4534
seenCr = false
46-
if scalar == lf {
47-
continue
48-
}
35+
continue
4936
}
50-
if scalar == cr {
51-
seenCr = true
52-
} else if scalar == lf {
37+
38+
seenCr = scalar == cr
39+
if scalar == cr || scalar == lf {
5340
lines.append(currentString)
5441
currentString = ""
5542
} else {
@@ -58,16 +45,14 @@ class UTF8LineParser {
5845
case .emptyInput:
5946
break Decode
6047
case .error(let len):
48+
seenCr = false
6149
if dataIter.position == data.endIndex {
6250
// Error at end of block, carry over in case of split code point
6351
remainderPos = data.index(data.endIndex, offsetBy: -len)
52+
// May as well break here as next will be .emptyInput
53+
break Decode
6454
} else {
6555
// Invalid character, replace with replacement character
66-
if seenCr {
67-
lines.append(currentString)
68-
currentString = ""
69-
seenCr = false
70-
}
7156
currentString.append(replacement)
7257
}
7358
}
@@ -77,26 +62,9 @@ class UTF8LineParser {
7762
return lines
7863
}
7964

80-
func closeAndReset() -> [String] {
81-
var lines: [String] = []
82-
83-
if seenCr {
84-
lines.append(currentString)
85-
currentString = ""
86-
}
87-
88-
if !remainder.isEmpty {
89-
currentString.append(replacement)
90-
remainder = Data()
91-
}
92-
93-
if !currentString.isEmpty {
94-
lines.append(currentString)
95-
}
96-
97-
currentString = ""
65+
func closeAndReset() {
9866
seenCr = false
99-
100-
return lines
67+
currentString = ""
68+
remainder = Data()
10169
}
10270
}

Tests/.swiftlint.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ disabled_rules:
33
opt_in_rules:
44
# Must specify even though enabled by default to update configuration of rule below.
55
- line_length
6+
- type_body_length
67

7-
# Provide a little extra lenience for test code line length.
8+
# Provide a little extra lenience for test code line and body length.
89
line_length:
910
warning: 140
11+
type_body_length:
12+
warning: 400
13+
error: 500
1014

1115
excluded:
1216
# Autogenerated manifest of tests

0 commit comments

Comments
 (0)