Skip to content

Commit d3fc367

Browse files
Merge pull request #858 from apollographql/add/headery-fun
GraphManager/Engine Headers + minor WebSocket cleanup
2 parents 0c56d45 + 6aa8c62 commit d3fc367

15 files changed

Lines changed: 484 additions & 150 deletions

Apollo.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
1111
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
1212
9B1A38532332AF6F00325FB4 /* String+SHA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1A38522332AF6F00325FB4 /* String+SHA.swift */; };
13+
9B1CCDD92360F02C007C9032 /* Bundle+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */; };
1314
9B64F6762354D219002D1BB5 /* URL+QueryDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B64F6752354D219002D1BB5 /* URL+QueryDict.swift */; };
1415
9B708AAD2305884500604A11 /* ApolloClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */; };
1516
9B78C71E2326E86E000C8C32 /* ErrorGenerationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B78C71B2326E859000C8C32 /* ErrorGenerationTests.swift */; };
@@ -269,6 +270,7 @@
269270
90690D2422433C8000FC2E54 /* Apollo-Target-PerformanceTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-PerformanceTests.xcconfig"; sourceTree = "<group>"; };
270271
90690D2522433CAF00FC2E54 /* Apollo-Target-TestSupport.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-TestSupport.xcconfig"; sourceTree = "<group>"; };
271272
9B1A38522332AF6F00325FB4 /* String+SHA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SHA.swift"; sourceTree = "<group>"; };
273+
9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Helpers.swift"; sourceTree = "<group>"; };
272274
9B64F6752354D219002D1BB5 /* URL+QueryDict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+QueryDict.swift"; sourceTree = "<group>"; };
273275
9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloClientProtocol.swift; sourceTree = "<group>"; };
274276
9B74BCBE2333F4ED00508F84 /* run-bundled-codegen.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "run-bundled-codegen.sh"; path = "scripts/run-bundled-codegen.sh"; sourceTree = SOURCE_ROOT; };
@@ -723,15 +725,16 @@
723725
9FCDFD211E33A09F007519DC /* Utilities */ = {
724726
isa = PBXGroup;
725727
children = (
726-
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
727728
9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */,
729+
9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */,
728730
9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */,
729731
9FE941CF1E62C771007CDD89 /* Promise.swift */,
730732
9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */,
731733
9F19D8431EED568200C57247 /* ResultOrPromise.swift */,
732734
9FEC15B31E681DAD00D461B4 /* Collections.swift */,
733735
9FADC8491E6B0B2300C677E6 /* Locking.swift */,
734736
9B1A38522332AF6F00325FB4 /* String+SHA.swift */,
737+
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
735738
);
736739
name = Utilities;
737740
sourceTree = "<group>";
@@ -1243,6 +1246,7 @@
12431246
9FF90A651DDDEB100034C3B6 /* GraphQLExecutor.swift in Sources */,
12441247
9FC750611D2A59C300458D91 /* GraphQLOperation.swift in Sources */,
12451248
9BDE43DF22C6708600FD7C7F /* GraphQLHTTPRequestError.swift in Sources */,
1249+
9B1CCDD92360F02C007C9032 /* Bundle+Helpers.swift in Sources */,
12461250
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */,
12471251
9FE941D01E62C771007CDD89 /* Promise.swift in Sources */,
12481252
9BA1245E22DE116B00BF1D24 /* Result+Helpers.swift in Sources */,

ApolloWebSocket.xcodeproj/project.pbxproj

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
7270747C206D13F400C131F6 /* StarWarsSubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */; };
1515
7270747D206D13F400C131F6 /* MockWebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707473206D112900C131F6 /* MockWebSocketTests.swift */; };
1616
7270747E206D13F400C131F6 /* StarWarsWebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707475206D112900C131F6 /* StarWarsWebSocketTests.swift */; };
17+
9B1CCDDB23610CDC007C9032 /* ApolloWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */; };
18+
9B1CCDDF236110C3007C9032 /* WebSocketError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDDE236110C3007C9032 /* WebSocketError.swift */; };
19+
9B1CCDE123611580007C9032 /* OperationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE023611580007C9032 /* OperationMessage.swift */; };
20+
9B1CCDE323611606007C9032 /* WebSocketTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE223611606007C9032 /* WebSocketTask.swift */; };
21+
9B1CCDEA23611B07007C9032 /* SplitNetworkTransportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */; };
1722
9F28B6CF206E488B00144A00 /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6CC206E487200144A00 /* Starscream.framework */; };
1823
9F28B6D520720F2F00144A00 /* Apollo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6D420720F2F00144A00 /* Apollo.framework */; };
1924
9F28B6D920720FD200144A00 /* ApolloTestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6D820720FD100144A00 /* ApolloTestSupport.framework */; };
@@ -59,6 +64,11 @@
5964
90690D0D224334C900FC2E54 /* ApolloWebSocket-Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Project-Release.xcconfig"; sourceTree = "<group>"; };
6065
90690D12224334FD00FC2E54 /* ApolloWebSocket-Target-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Target-Framework.xcconfig"; sourceTree = "<group>"; };
6166
90690D132243350400FC2E54 /* ApolloWebSocket-Target-Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Target-Tests.xcconfig"; sourceTree = "<group>"; };
67+
9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloWebSocket.swift; sourceTree = "<group>"; };
68+
9B1CCDDE236110C3007C9032 /* WebSocketError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketError.swift; sourceTree = "<group>"; };
69+
9B1CCDE023611580007C9032 /* OperationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationMessage.swift; sourceTree = "<group>"; };
70+
9B1CCDE223611606007C9032 /* WebSocketTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketTask.swift; sourceTree = "<group>"; };
71+
9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplitNetworkTransportTests.swift; path = Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift; sourceTree = SOURCE_ROOT; };
6272
9F28B6C6206E487200144A00 /* Starscream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Starscream.xcodeproj; path = Carthage/Checkouts/Starscream/Starscream.xcodeproj; sourceTree = "<group>"; };
6373
9F28B6D420720F2F00144A00 /* Apollo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Apollo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6474
9F28B6D820720FD100144A00 /* ApolloTestSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ApolloTestSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -124,7 +134,11 @@
124134
72707469206D111A00C131F6 /* ApolloWebSocket */ = {
125135
isa = PBXGroup;
126136
children = (
137+
9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */,
138+
9B1CCDE023611580007C9032 /* OperationMessage.swift */,
127139
7270746A206D111A00C131F6 /* SplitNetworkTransport.swift */,
140+
9B1CCDDE236110C3007C9032 /* WebSocketError.swift */,
141+
9B1CCDE223611606007C9032 /* WebSocketTask.swift */,
128142
7270746B206D111A00C131F6 /* WebSocketTransport.swift */,
129143
7270746C206D111A00C131F6 /* Info.plist */,
130144
);
@@ -136,10 +150,11 @@
136150
isa = PBXGroup;
137151
children = (
138152
72707471206D112900C131F6 /* MockWebSocket.swift */,
139-
72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */,
140153
72707473206D112900C131F6 /* MockWebSocketTests.swift */,
141-
72707474206D112900C131F6 /* Info.plist */,
154+
9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */,
155+
72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */,
142156
72707475206D112900C131F6 /* StarWarsWebSocketTests.swift */,
157+
72707474206D112900C131F6 /* Info.plist */,
143158
);
144159
name = ApolloWebSocketTests;
145160
path = Tests/ApolloWebSocketTests;
@@ -292,8 +307,12 @@
292307
isa = PBXSourcesBuildPhase;
293308
buildActionMask = 2147483647;
294309
files = (
310+
9B1CCDDF236110C3007C9032 /* WebSocketError.swift in Sources */,
295311
7270746D206D111A00C131F6 /* SplitNetworkTransport.swift in Sources */,
312+
9B1CCDE323611606007C9032 /* WebSocketTask.swift in Sources */,
296313
7270746E206D111A00C131F6 /* WebSocketTransport.swift in Sources */,
314+
9B1CCDE123611580007C9032 /* OperationMessage.swift in Sources */,
315+
9B1CCDDB23610CDC007C9032 /* ApolloWebSocket.swift in Sources */,
297316
);
298317
runOnlyForDeploymentPostprocessing = 0;
299318
};
@@ -302,6 +321,7 @@
302321
buildActionMask = 2147483647;
303322
files = (
304323
7270747B206D13F400C131F6 /* MockWebSocket.swift in Sources */,
324+
9B1CCDEA23611B07007C9032 /* SplitNetworkTransportTests.swift in Sources */,
305325
7270747C206D13F400C131F6 /* StarWarsSubscriptionTests.swift in Sources */,
306326
7270747D206D13F400C131F6 /* MockWebSocketTests.swift in Sources */,
307327
7270747E206D13F400C131F6 /* StarWarsWebSocketTests.swift in Sources */,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Bundle+Helpers.swift
3+
// Apollo
4+
//
5+
// Created by Ellen Shapiro on 10/23/19.
6+
// Copyright © 2019 Apollo GraphQL. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
extension Bundle {
12+
13+
/// Type-safe getter for info dictionary key objects
14+
///
15+
/// - Parameter key: The key to try to grab an object for
16+
/// - Returns: The object of the desired type, or nil if it is not present or of the incorrect type.
17+
func bundleValue<T>(forKey key: String) -> T? {
18+
return object(forInfoDictionaryKey: key) as? T
19+
}
20+
21+
/// The bundle identifier of this bundle, or nil if not present.
22+
var bundleIdentifier: String? {
23+
return self.bundleValue(forKey: String(kCFBundleIdentifierKey))
24+
}
25+
26+
/// The build number of this bundle (kCFBundleVersion) as a string, or nil if not present.
27+
var buildNumber: String? {
28+
return self.bundleValue(forKey: String(kCFBundleVersionKey))
29+
}
30+
31+
/// The short version string for this bundle, or nil if not present.
32+
var shortVersion: String? {
33+
return self.bundleValue(forKey: "CFBundleShortVersionString")
34+
}
35+
}

Sources/Apollo/HTTPNetworkTransport.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public class HTTPNetworkTransport {
9494
private let requestCreator: RequestCreator
9595
private let sendOperationIdentifiers: Bool
9696

97+
public lazy var clientName = HTTPNetworkTransport.defaultClientName
98+
public lazy var clientVersion = HTTPNetworkTransport.defaultClientVersion
99+
97100
/// Creates a network transport with the specified server URL and session configuration.
98101
///
99102
/// - Parameters:
@@ -359,6 +362,8 @@ public class HTTPNetworkTransport {
359362
sendQueryDocument: sendQueryDocument,
360363
autoPersistQuery: autoPersistQueries)
361364
var request = URLRequest(url: self.url)
365+
request.setValue(self.clientName, forHTTPHeaderField: HTTPNetworkTransport.headerFieldNameClientName)
366+
request.setValue(self.clientVersion, forHTTPHeaderField: HTTPNetworkTransport.headerFieldNameClientVersion)
362367

363368
// We default to json, but this can be changed below if needed.
364369
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

Sources/Apollo/NetworkTransport.swift

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// A network transport is responsible for sending GraphQL operations to a server.
2-
public protocol NetworkTransport {
2+
public protocol NetworkTransport: class {
33

44
/// Send a GraphQL operation to a server and return a response.
55
///
@@ -8,8 +8,60 @@ public protocol NetworkTransport {
88
/// - completionHandler: A closure to call when a request completes. On `success` will contain the response received from the server. On `failure` will contain the error which occurred.
99
/// - Returns: An object that can be used to cancel an in progress request.
1010
func send<Operation>(operation: Operation, completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable
11+
12+
/// The name of the client to send as the `"apollographql-client-name"` header.
13+
var clientName: String { get set }
14+
15+
/// The version of the client to send as the `"apollographql-client-version"` header
16+
var clientVersion: String { get set }
1117
}
1218

19+
public extension NetworkTransport {
20+
21+
/// The header field name for the Client Name
22+
static var headerFieldNameClientName: String {
23+
return "apollographql-client-name"
24+
}
25+
26+
/// The header field name for the client version
27+
static var headerFieldNameClientVersion: String {
28+
return "apollographql-client-version"
29+
}
30+
31+
/// The default client name to use when setting up the `clientName` property
32+
static var defaultClientName: String {
33+
guard let identifier = Bundle.main.bundleIdentifier else {
34+
return "apollo-ios-client"
35+
}
36+
37+
return "\(identifier)-apollo-ios"
38+
}
39+
40+
/// The default client version to use when setting up the `clientVersion` property.
41+
static var defaultClientVersion: String {
42+
var version = String()
43+
if let shortVersion = Bundle.main.shortVersion {
44+
version.append(shortVersion)
45+
}
46+
47+
if let buildNumber = Bundle.main.buildNumber {
48+
if version.isEmpty {
49+
version.append(buildNumber)
50+
} else {
51+
version.append("-\(buildNumber)")
52+
}
53+
}
54+
55+
if version.isEmpty {
56+
version = "(unknown)"
57+
}
58+
59+
return version
60+
}
61+
}
62+
63+
// MARK: -
64+
1365
/// A network transport which can also handle uploads of files.
1466
public protocol UploadingNetworkTransport: NetworkTransport {
1567

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Starscream
2+
import Foundation
3+
4+
// MARK: - Client protocol
5+
6+
/// Protocol allowing alternative implementations of web sockets beyond `ApolloWebSocket`. Extends `Starscream`'s `WebSocketClient` protocol.
7+
public protocol ApolloWebSocketClient: WebSocketClient {
8+
9+
/// Required initializer
10+
///
11+
/// - Parameter request: The URLRequest to use on connection.
12+
/// - Parameter protocols: The supported protocols
13+
init(request: URLRequest, protocols: [String]?)
14+
15+
/// The URLRequest used on connection.
16+
var request: URLRequest { get set }
17+
}
18+
19+
// MARK: - WebSocket
20+
21+
/// Included implementation of an `ApolloWebSocketClient`, based on `Starscream`'s `WebSocket`.
22+
public class ApolloWebSocket: WebSocket, ApolloWebSocketClient {
23+
required public convenience init(request: URLRequest, protocols: [String]? = nil) {
24+
self.init(request: request, protocols: protocols, stream: FoundationStream())
25+
}
26+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#if !COCOAPODS
2+
import Apollo
3+
#endif
4+
import Foundation
5+
6+
final class OperationMessage {
7+
enum Types : String {
8+
case connectionInit = "connection_init" // Client -> Server
9+
case connectionTerminate = "connection_terminate" // Client -> Server
10+
case start = "start" // Client -> Server
11+
case stop = "stop" // Client -> Server
12+
13+
case connectionAck = "connection_ack" // Server -> Client
14+
case connectionError = "connection_error" // Server -> Client
15+
case connectionKeepAlive = "ka" // Server -> Client
16+
case data = "data" // Server -> Client
17+
case error = "error" // Server -> Client
18+
case complete = "complete" // Server -> Client
19+
}
20+
21+
let serializationFormat = JSONSerializationFormat.self
22+
var message: GraphQLMap = [:]
23+
var serialized: String?
24+
25+
var rawMessage : String? {
26+
let serialized = try! serializationFormat.serialize(value: message)
27+
if let str = String(data: serialized, encoding: .utf8) {
28+
return str
29+
} else {
30+
return nil
31+
}
32+
}
33+
34+
init(payload: GraphQLMap? = nil, id: String? = nil, type: Types = .start) {
35+
if let payload = payload {
36+
message += ["payload": payload]
37+
}
38+
if let id = id {
39+
message += ["id": id]
40+
}
41+
message += ["type": type.rawValue]
42+
}
43+
44+
init(serialized: String) {
45+
self.serialized = serialized
46+
}
47+
48+
func parse(handler: (_ type: String?, _ id: String?, _ payload: JSONObject?, _ error: Error?) -> Void) {
49+
guard let serialized = self.serialized else {
50+
handler(nil, nil, nil, WebSocketError(payload: nil, error: nil, kind: .serializedMessageError))
51+
return
52+
}
53+
54+
guard let data = self.serialized?.data(using: (.utf8) ) else {
55+
handler(nil, nil, nil, WebSocketError(payload: nil, error: nil, kind: .unprocessedMessage(serialized)))
56+
return
57+
}
58+
59+
var type : String?
60+
var id : String?
61+
var payload : JSONObject?
62+
63+
do {
64+
let json = try JSONSerializationFormat.deserialize(data: data ) as? JSONObject
65+
66+
id = json?["id"] as? String
67+
type = json?["type"] as? String
68+
payload = json?["payload"] as? JSONObject
69+
70+
handler(type,id,payload,nil)
71+
}
72+
catch {
73+
handler(type, id, payload, WebSocketError(payload: payload, error: error, kind: .unprocessedMessage(serialized)))
74+
}
75+
}
76+
}

Sources/ApolloWebSocket/SplitNetworkTransport.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ public class SplitNetworkTransport {
77
private let httpNetworkTransport: UploadingNetworkTransport
88
private let webSocketNetworkTransport: NetworkTransport
99

10+
public var clientName: String {
11+
get {
12+
let httpName = self.httpNetworkTransport.clientName
13+
let websocketName = self.webSocketNetworkTransport.clientName
14+
if httpName == websocketName {
15+
return httpName
16+
} else {
17+
return "SPLIT_HTTPNAME_\(httpName)_WEBSOCKETNAME_\(websocketName)"
18+
}
19+
}
20+
set {
21+
self.httpNetworkTransport.clientName = newValue
22+
self.webSocketNetworkTransport.clientName = newValue
23+
}
24+
}
25+
26+
public var clientVersion: String {
27+
get {
28+
let httpVersion = self.httpNetworkTransport.clientVersion
29+
let websocketVersion = self.webSocketNetworkTransport.clientVersion
30+
if httpVersion == websocketVersion {
31+
return httpVersion
32+
} else {
33+
return "SPLIT_HTTPVERSION_\(httpVersion)_WEBSOCKETNAME_\(websocketVersion)"
34+
}
35+
}
36+
set {
37+
self.httpNetworkTransport.clientVersion = newValue
38+
self.webSocketNetworkTransport.clientVersion = newValue
39+
}
40+
}
41+
1042
/// Designated initializer
1143
///
1244
/// - Parameters:

0 commit comments

Comments
 (0)