Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
9B1A38532332AF6F00325FB4 /* String+SHA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1A38522332AF6F00325FB4 /* String+SHA.swift */; };
9B1CCDD92360F02C007C9032 /* Bundle+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */; };
9B64F6762354D219002D1BB5 /* URL+QueryDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B64F6752354D219002D1BB5 /* URL+QueryDict.swift */; };
9B708AAD2305884500604A11 /* ApolloClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */; };
9B78C71E2326E86E000C8C32 /* ErrorGenerationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B78C71B2326E859000C8C32 /* ErrorGenerationTests.swift */; };
Expand Down Expand Up @@ -269,6 +270,7 @@
90690D2422433C8000FC2E54 /* Apollo-Target-PerformanceTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-PerformanceTests.xcconfig"; sourceTree = "<group>"; };
90690D2522433CAF00FC2E54 /* Apollo-Target-TestSupport.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-TestSupport.xcconfig"; sourceTree = "<group>"; };
9B1A38522332AF6F00325FB4 /* String+SHA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SHA.swift"; sourceTree = "<group>"; };
9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Helpers.swift"; sourceTree = "<group>"; };
9B64F6752354D219002D1BB5 /* URL+QueryDict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+QueryDict.swift"; sourceTree = "<group>"; };
9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloClientProtocol.swift; sourceTree = "<group>"; };
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; };
Expand Down Expand Up @@ -723,15 +725,16 @@
9FCDFD211E33A09F007519DC /* Utilities */ = {
isa = PBXGroup;
children = (
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */,
9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */,
9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */,
9FE941CF1E62C771007CDD89 /* Promise.swift */,
9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */,
9F19D8431EED568200C57247 /* ResultOrPromise.swift */,
9FEC15B31E681DAD00D461B4 /* Collections.swift */,
9FADC8491E6B0B2300C677E6 /* Locking.swift */,
9B1A38522332AF6F00325FB4 /* String+SHA.swift */,
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
);
name = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -1243,6 +1246,7 @@
9FF90A651DDDEB100034C3B6 /* GraphQLExecutor.swift in Sources */,
9FC750611D2A59C300458D91 /* GraphQLOperation.swift in Sources */,
9BDE43DF22C6708600FD7C7F /* GraphQLHTTPRequestError.swift in Sources */,
9B1CCDD92360F02C007C9032 /* Bundle+Helpers.swift in Sources */,
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */,
9FE941D01E62C771007CDD89 /* Promise.swift in Sources */,
9BA1245E22DE116B00BF1D24 /* Result+Helpers.swift in Sources */,
Expand Down
24 changes: 22 additions & 2 deletions ApolloWebSocket.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
7270747C206D13F400C131F6 /* StarWarsSubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */; };
7270747D206D13F400C131F6 /* MockWebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707473206D112900C131F6 /* MockWebSocketTests.swift */; };
7270747E206D13F400C131F6 /* StarWarsWebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72707475206D112900C131F6 /* StarWarsWebSocketTests.swift */; };
9B1CCDDB23610CDC007C9032 /* ApolloWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */; };
9B1CCDDF236110C3007C9032 /* WebSocketError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDDE236110C3007C9032 /* WebSocketError.swift */; };
9B1CCDE123611580007C9032 /* OperationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE023611580007C9032 /* OperationMessage.swift */; };
9B1CCDE323611606007C9032 /* WebSocketTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE223611606007C9032 /* WebSocketTask.swift */; };
9B1CCDEA23611B07007C9032 /* SplitNetworkTransportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */; };
9F28B6CF206E488B00144A00 /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6CC206E487200144A00 /* Starscream.framework */; };
9F28B6D520720F2F00144A00 /* Apollo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6D420720F2F00144A00 /* Apollo.framework */; };
9F28B6D920720FD200144A00 /* ApolloTestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F28B6D820720FD100144A00 /* ApolloTestSupport.framework */; };
Expand Down Expand Up @@ -59,6 +64,11 @@
90690D0D224334C900FC2E54 /* ApolloWebSocket-Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Project-Release.xcconfig"; sourceTree = "<group>"; };
90690D12224334FD00FC2E54 /* ApolloWebSocket-Target-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Target-Framework.xcconfig"; sourceTree = "<group>"; };
90690D132243350400FC2E54 /* ApolloWebSocket-Target-Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ApolloWebSocket-Target-Tests.xcconfig"; sourceTree = "<group>"; };
9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloWebSocket.swift; sourceTree = "<group>"; };
9B1CCDDE236110C3007C9032 /* WebSocketError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketError.swift; sourceTree = "<group>"; };
9B1CCDE023611580007C9032 /* OperationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationMessage.swift; sourceTree = "<group>"; };
9B1CCDE223611606007C9032 /* WebSocketTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketTask.swift; sourceTree = "<group>"; };
9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplitNetworkTransportTests.swift; path = Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift; sourceTree = SOURCE_ROOT; };
9F28B6C6206E487200144A00 /* Starscream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Starscream.xcodeproj; path = Carthage/Checkouts/Starscream/Starscream.xcodeproj; sourceTree = "<group>"; };
9F28B6D420720F2F00144A00 /* Apollo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Apollo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9F28B6D820720FD100144A00 /* ApolloTestSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ApolloTestSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -124,7 +134,11 @@
72707469206D111A00C131F6 /* ApolloWebSocket */ = {
isa = PBXGroup;
children = (
9B1CCDDA23610CDC007C9032 /* ApolloWebSocket.swift */,
9B1CCDE023611580007C9032 /* OperationMessage.swift */,
7270746A206D111A00C131F6 /* SplitNetworkTransport.swift */,
9B1CCDDE236110C3007C9032 /* WebSocketError.swift */,
9B1CCDE223611606007C9032 /* WebSocketTask.swift */,
7270746B206D111A00C131F6 /* WebSocketTransport.swift */,
7270746C206D111A00C131F6 /* Info.plist */,
);
Expand All @@ -136,10 +150,11 @@
isa = PBXGroup;
children = (
72707471206D112900C131F6 /* MockWebSocket.swift */,
72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */,
72707473206D112900C131F6 /* MockWebSocketTests.swift */,
72707474206D112900C131F6 /* Info.plist */,
9B1CCDE923611B07007C9032 /* SplitNetworkTransportTests.swift */,
72707472206D112900C131F6 /* StarWarsSubscriptionTests.swift */,
72707475206D112900C131F6 /* StarWarsWebSocketTests.swift */,
72707474206D112900C131F6 /* Info.plist */,
);
name = ApolloWebSocketTests;
path = Tests/ApolloWebSocketTests;
Expand Down Expand Up @@ -292,8 +307,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9B1CCDDF236110C3007C9032 /* WebSocketError.swift in Sources */,
7270746D206D111A00C131F6 /* SplitNetworkTransport.swift in Sources */,
9B1CCDE323611606007C9032 /* WebSocketTask.swift in Sources */,
7270746E206D111A00C131F6 /* WebSocketTransport.swift in Sources */,
9B1CCDE123611580007C9032 /* OperationMessage.swift in Sources */,
9B1CCDDB23610CDC007C9032 /* ApolloWebSocket.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -302,6 +321,7 @@
buildActionMask = 2147483647;
files = (
7270747B206D13F400C131F6 /* MockWebSocket.swift in Sources */,
9B1CCDEA23611B07007C9032 /* SplitNetworkTransportTests.swift in Sources */,
7270747C206D13F400C131F6 /* StarWarsSubscriptionTests.swift in Sources */,
7270747D206D13F400C131F6 /* MockWebSocketTests.swift in Sources */,
7270747E206D13F400C131F6 /* StarWarsWebSocketTests.swift in Sources */,
Expand Down
35 changes: 35 additions & 0 deletions Sources/Apollo/Bundle+Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Bundle+Helpers.swift
// Apollo
//
// Created by Ellen Shapiro on 10/23/19.
// Copyright © 2019 Apollo GraphQL. All rights reserved.
//

import Foundation

extension Bundle {

/// Type-safe getter for info dictionary key objects
///
/// - Parameter key: The key to try to grab an object for
/// - Returns: The object of the desired type, or nil if it is not present or of the incorrect type.
func bundleValue<T>(forKey key: String) -> T? {
return object(forInfoDictionaryKey: key) as? T
}

/// The bundle identifier of this bundle, or nil if not present.
var bundleIdentifier: String? {
return self.bundleValue(forKey: String(kCFBundleIdentifierKey))
}

/// The build number of this bundle (kCFBundleVersion) as a string, or nil if not present.
var buildNumber: String? {
return self.bundleValue(forKey: String(kCFBundleVersionKey))
}

/// The short version string for this bundle, or nil if not present.
var shortVersion: String? {
return self.bundleValue(forKey: "CFBundleShortVersionString")
}
}
5 changes: 5 additions & 0 deletions Sources/Apollo/HTTPNetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class HTTPNetworkTransport {
private let requestCreator: RequestCreator
private let sendOperationIdentifiers: Bool

public lazy var clientName = HTTPNetworkTransport.defaultClientName
public lazy var clientVersion = HTTPNetworkTransport.defaultClientVersion

/// Creates a network transport with the specified server URL and session configuration.
///
/// - Parameters:
Expand Down Expand Up @@ -359,6 +362,8 @@ public class HTTPNetworkTransport {
sendQueryDocument: sendQueryDocument,
autoPersistQuery: autoPersistQueries)
var request = URLRequest(url: self.url)
request.setValue(self.clientName, forHTTPHeaderField: HTTPNetworkTransport.headerFieldNameClientName)
request.setValue(self.clientVersion, forHTTPHeaderField: HTTPNetworkTransport.headerFieldNameClientVersion)

// We default to json, but this can be changed below if needed.
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
Expand Down
54 changes: 53 additions & 1 deletion Sources/Apollo/NetworkTransport.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// A network transport is responsible for sending GraphQL operations to a server.
public protocol NetworkTransport {
public protocol NetworkTransport: class {

/// Send a GraphQL operation to a server and return a response.
///
Expand All @@ -8,8 +8,60 @@ public protocol NetworkTransport {
/// - 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.
/// - Returns: An object that can be used to cancel an in progress request.
func send<Operation>(operation: Operation, completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable

/// The name of the client to send as the `"apollographql-client-name"` header.
var clientName: String { get set }

/// The version of the client to send as the `"apollographql-client-version"` header
var clientVersion: String { get set }
}

public extension NetworkTransport {

/// The header field name for the Client Name
static var headerFieldNameClientName: String {
return "apollographql-client-name"
}

/// The header field name for the client version
static var headerFieldNameClientVersion: String {
return "apollographql-client-version"
}

/// The default client name to use when setting up the `clientName` property
static var defaultClientName: String {
guard let identifier = Bundle.main.bundleIdentifier else {
return "apollo-ios-client"
}

return "\(identifier)-apollo-ios"
}

/// The default client version to use when setting up the `clientVersion` property.
static var defaultClientVersion: String {
var version = String()
if let shortVersion = Bundle.main.shortVersion {
version.append(shortVersion)
}

if let buildNumber = Bundle.main.buildNumber {
if version.isEmpty {
version.append(buildNumber)
} else {
version.append("-\(buildNumber)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually decided against this on my end as I'm increasing the build number on each build (based of number of commits) and it makes the "Clients" view in Apollo Engine a bit noisy for the non-public environments/variants. YMMV of course and it's always configurable anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah in my past experience when I've had build number auto-incrementing it's actually been quite helpful in being able to identify exactly where I broke something 😄. And as you said, it's configurable, so if people don't like the default, they can always adjust.

}
}

if version.isEmpty {
version = "(unknown)"
}

return version
}
}

// MARK: -

/// A network transport which can also handle uploads of files.
public protocol UploadingNetworkTransport: NetworkTransport {

Expand Down
26 changes: 26 additions & 0 deletions Sources/ApolloWebSocket/ApolloWebSocket.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Starscream
import Foundation

// MARK: - Client protocol

/// Protocol allowing alternative implementations of web sockets beyond `ApolloWebSocket`. Extends `Starscream`'s `WebSocketClient` protocol.
public protocol ApolloWebSocketClient: WebSocketClient {

/// Required initializer
///
/// - Parameter request: The URLRequest to use on connection.
/// - Parameter protocols: The supported protocols
init(request: URLRequest, protocols: [String]?)

/// The URLRequest used on connection.
var request: URLRequest { get set }
}

// MARK: - WebSocket

/// Included implementation of an `ApolloWebSocketClient`, based on `Starscream`'s `WebSocket`.
public class ApolloWebSocket: WebSocket, ApolloWebSocketClient {
required public convenience init(request: URLRequest, protocols: [String]? = nil) {
self.init(request: request, protocols: protocols, stream: FoundationStream())
}
}
76 changes: 76 additions & 0 deletions Sources/ApolloWebSocket/OperationMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#if !COCOAPODS
import Apollo
#endif
import Foundation

final class OperationMessage {
enum Types : String {
case connectionInit = "connection_init" // Client -> Server
case connectionTerminate = "connection_terminate" // Client -> Server
case start = "start" // Client -> Server
case stop = "stop" // Client -> Server

case connectionAck = "connection_ack" // Server -> Client
case connectionError = "connection_error" // Server -> Client
case connectionKeepAlive = "ka" // Server -> Client
case data = "data" // Server -> Client
case error = "error" // Server -> Client
case complete = "complete" // Server -> Client
}

let serializationFormat = JSONSerializationFormat.self
var message: GraphQLMap = [:]
var serialized: String?

var rawMessage : String? {
let serialized = try! serializationFormat.serialize(value: message)
if let str = String(data: serialized, encoding: .utf8) {
return str
} else {
return nil
}
}

init(payload: GraphQLMap? = nil, id: String? = nil, type: Types = .start) {
if let payload = payload {
message += ["payload": payload]
}
if let id = id {
message += ["id": id]
}
message += ["type": type.rawValue]
}

init(serialized: String) {
self.serialized = serialized
}

func parse(handler: (_ type: String?, _ id: String?, _ payload: JSONObject?, _ error: Error?) -> Void) {
guard let serialized = self.serialized else {
handler(nil, nil, nil, WebSocketError(payload: nil, error: nil, kind: .serializedMessageError))
return
}

guard let data = self.serialized?.data(using: (.utf8) ) else {
handler(nil, nil, nil, WebSocketError(payload: nil, error: nil, kind: .unprocessedMessage(serialized)))
return
}

var type : String?
var id : String?
var payload : JSONObject?

do {
let json = try JSONSerializationFormat.deserialize(data: data ) as? JSONObject

id = json?["id"] as? String
type = json?["type"] as? String
payload = json?["payload"] as? JSONObject

handler(type,id,payload,nil)
}
catch {
handler(type, id, payload, WebSocketError(payload: payload, error: error, kind: .unprocessedMessage(serialized)))
}
}
}
32 changes: 32 additions & 0 deletions Sources/ApolloWebSocket/SplitNetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,38 @@ public class SplitNetworkTransport {
private let httpNetworkTransport: UploadingNetworkTransport
private let webSocketNetworkTransport: NetworkTransport

public var clientName: String {
get {
let httpName = self.httpNetworkTransport.clientName
let websocketName = self.webSocketNetworkTransport.clientName
if httpName == websocketName {
return httpName
} else {
return "SPLIT_HTTPNAME_\(httpName)_WEBSOCKETNAME_\(websocketName)"
}
}
set {
self.httpNetworkTransport.clientName = newValue
self.webSocketNetworkTransport.clientName = newValue
}
}

public var clientVersion: String {
get {
let httpVersion = self.httpNetworkTransport.clientVersion
let websocketVersion = self.webSocketNetworkTransport.clientVersion
if httpVersion == websocketVersion {
return httpVersion
} else {
return "SPLIT_HTTPVERSION_\(httpVersion)_WEBSOCKETNAME_\(websocketVersion)"
}
}
set {
self.httpNetworkTransport.clientVersion = newValue
self.webSocketNetworkTransport.clientVersion = newValue
}
}

/// Designated initializer
///
/// - Parameters:
Expand Down
Loading