Skip to content

Commit 6a17912

Browse files
committed
WIP: Network fetch returns data as chunks
1 parent 2bfb7c1 commit 6a17912

12 files changed

+291
-256
lines changed

apollo-ios/Sources/Apollo/ApolloClient.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public class ApolloClient: ApolloClientProtocol, @unchecked Sendable {
3636

3737
public let store: ApolloStore
3838

39-
private let sendEnhancedClientAwareness: Bool
40-
4139
public enum ApolloClientError: Error, LocalizedError, Hashable {
4240
case noUploadTransport
4341
case noSubscriptionTransport
@@ -62,33 +60,30 @@ public class ApolloClient: ApolloClientProtocol, @unchecked Sendable {
6260
/// key. Client library metadata is the Apollo iOS library name and version. Defaults to `true`.
6361
public init(
6462
networkTransport: any NetworkTransport,
65-
store: ApolloStore,
66-
sendEnhancedClientAwareness: Bool = true
63+
store: ApolloStore
6764
) {
6865
self.networkTransport = networkTransport
6966
self.store = store
70-
self.sendEnhancedClientAwareness = sendEnhancedClientAwareness
7167
}
7268

7369
/// Creates a client with a `RequestChainNetworkTransport` connecting to the specified URL.
7470
///
7571
/// - Parameter url: The URL of a GraphQL server to connect to.
7672
public convenience init(
7773
url: URL,
78-
sendEnhancedClientAwareness: Bool = true
74+
clientAwarenessMetadata: ClientAwarenessMetadata = ClientAwarenessMetadata()
7975
) {
8076
let store = ApolloStore(cache: InMemoryNormalizedCache())
8177
let provider = DefaultInterceptorProvider(store: store)
8278
let transport = RequestChainNetworkTransport(
8379
interceptorProvider: provider,
8480
endpointURL: url,
85-
sendEnhancedClientAwareness: sendEnhancedClientAwareness
81+
clientAwarenessMetadata: clientAwarenessMetadata
8682
)
8783

8884
self.init(
8985
networkTransport: transport,
90-
store: store,
91-
sendEnhancedClientAwareness: sendEnhancedClientAwareness
86+
store: store
9287
)
9388
}
9489

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import Foundation
2+
#if !COCOAPODS
3+
import ApolloAPI
4+
#endif
5+
6+
/// A data structure containing telemetry metadata about the client. This is used by GraphOS Studio's
7+
/// [client awareness](https://www.apollographql.com/docs/graphos/platform/insights/client-segmentation)
8+
/// feature.
9+
public struct ClientAwarenessMetadata: Sendable {
10+
11+
/// The name of the application. This value is sent for the header "apollographql-client-name".
12+
///
13+
/// Defaults to `nil`.
14+
public var clientApplicationName: String?
15+
16+
/// The version of the application. This value is sent for the header "apollographql-client-version".
17+
///
18+
/// Defaults to `nil`.
19+
public var clientApplicationVersion: String?
20+
21+
/// Determines if the Apollo iOS library name and version should be sent with the telemetry data.
22+
///
23+
/// If `true`, the JSON body of the request will include a "clientLibrary" extension containing
24+
/// the name of the Apollo iOS library and the version of Apollo iOS being used by the client
25+
/// application.
26+
///
27+
/// Defaults to `true`.
28+
public var includeApolloLibraryAwareness: Bool = true
29+
30+
public init(
31+
clientApplicationName: String? = nil,
32+
clientApplicationVersion: String? = nil,
33+
includeApolloLibraryAwareness: Bool = true
34+
) {
35+
self.clientApplicationName = clientApplicationName
36+
self.clientApplicationVersion = clientApplicationVersion
37+
self.includeApolloLibraryAwareness = includeApolloLibraryAwareness
38+
}
39+
40+
/// Disables all client awareness metadata.
41+
public static var none: ClientAwarenessMetadata {
42+
.init(
43+
clientApplicationName: nil,
44+
clientApplicationVersion: nil,
45+
includeApolloLibraryAwareness: false
46+
)
47+
}
48+
49+
/// Enables all client awareness metadata with the following default values:
50+
///
51+
/// - `clientApplicationName`: The application's bundle identifier + "-apollo-ios".
52+
/// - `clientApplicationVersion`: The bundle's short version string if available,
53+
/// otherwise the build number.
54+
/// - `includeApolloiOSLibraryVersion`: `true`
55+
public static var enabledWithDefaults: ClientAwarenessMetadata {
56+
.init(
57+
clientApplicationName: defaultClientName,
58+
clientApplicationVersion: defaultClientVersion,
59+
includeApolloLibraryAwareness: true
60+
)
61+
}
62+
63+
/// The default client name to use when setting up the `clientName` property
64+
public static var defaultClientName: String {
65+
guard let identifier = Bundle.main.bundleIdentifier else {
66+
return "apollo-ios-client"
67+
}
68+
69+
return "\(identifier)-apollo-ios"
70+
}
71+
72+
/// The default client version to use when setting up the `clientVersion` property.
73+
public static var defaultClientVersion: String {
74+
var version = String()
75+
if let shortVersion = Bundle.main.shortVersion {
76+
version.append(shortVersion)
77+
}
78+
79+
if let buildNumber = Bundle.main.buildNumber {
80+
if version.isEmpty {
81+
version.append(buildNumber)
82+
} else {
83+
version.append("-\(buildNumber)")
84+
}
85+
}
86+
87+
if version.isEmpty {
88+
version = "(unknown)"
89+
}
90+
91+
return version
92+
}
93+
94+
struct Constants {
95+
/// The field name for the Apollo Client Name header
96+
static let clientApplicationNameKey: StaticString = "apollographql-client-name"
97+
98+
/// The field name for the Apollo Client Version header
99+
static let clientApplicationVersionKey: StaticString = "apollographql-client-version"
100+
}
101+
102+
/// A helper method that adds the client awareness headers to the given request
103+
/// This is used by GraphOS Studio's
104+
/// [client awareness](https://www.apollographql.com/docs/graphos/platform/insights/client-segmentation)
105+
/// feature.
106+
///
107+
/// - Parameters:
108+
/// - clientAwarenessMetadata: The client name. The telemetry metadata about the client.
109+
public func applyHeaders(
110+
to request: inout URLRequest
111+
) {
112+
if let clientApplicationName = self.clientApplicationName {
113+
request.addValue(
114+
clientApplicationName,
115+
forHTTPHeaderField: ClientAwarenessMetadata.Constants.clientApplicationNameKey.description
116+
)
117+
}
118+
119+
if let clientApplicationVersion = self.clientApplicationVersion {
120+
request.addValue(
121+
clientApplicationVersion,
122+
forHTTPHeaderField: ClientAwarenessMetadata.Constants.clientApplicationVersionKey.description
123+
)
124+
}
125+
}
126+
127+
/// Adds client metadata to the request body in the `extensions` key.
128+
///
129+
/// - Parameter body: The previously generated JSON body.
130+
func applyExtension(to body: inout JSONEncodableDictionary) {
131+
if self.includeApolloLibraryAwareness {
132+
let clientLibraryMetadata: JSONEncodableDictionary = [
133+
"name": Apollo.Constants.ApolloClientName,
134+
"version": Apollo.Constants.ApolloClientVersion
135+
]
136+
137+
var extensions = body["extensions"] as? JSONEncodableDictionary ?? JSONEncodableDictionary()
138+
extensions["clientLibrary"] = clientLibraryMetadata
139+
140+
body["extensions"] = extensions
141+
}
142+
}
143+
}

apollo-ios/Sources/Apollo/GraphQLRequest.swift

Lines changed: 21 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,52 @@ public protocol GraphQLRequest<Operation>: Sendable {
2121
/// [optional] A context that is being passed through the request chain.
2222
var context: (any RequestContext)? { get set }
2323

24+
/// The telemetry metadata about the client. This is used by GraphOS Studio's
25+
/// [client awareness](https://www.apollographql.com/docs/graphos/platform/insights/client-segmentation)
26+
/// feature.
27+
var clientAwarenessMetadata: ClientAwarenessMetadata { get }
28+
29+
/// Converts the receiver into a `URLRequest` to be used for networking operations.
30+
///
31+
/// - Note: This function should call `createDefaultRequest()` to obtain a request with the
32+
/// default configuration. The implementation may then modify that request. See the documentation
33+
/// for ``GraphQLRequest/createDefaultRequest()`` for more information.
2434
func toURLRequest() throws -> URLRequest
2535
}
2636

37+
public extension GraphQLRequest {
38+
var clientAwarenessMetadata: ClientAwarenessMetadata { .init() }
39+
}
40+
2741
// MARK: - Helper Functions
2842

2943
extension GraphQLRequest {
3044

3145
/// Creates a default `URLRequest` for the receiver.
3246
///
33-
/// This can be called within the implementation of `toURLRequest()` and the returned request
34-
/// can then be modified as necessary before being returned.
35-
///
3647
/// This function creates a `URLRequest` with the following behaviors:
3748
/// - `url` set to the receiver's `graphQLEndpoint`
3849
/// - `httpMethod` set to POST
50+
/// - Client awareness headers from `clientAwarenessMetadata` added to `allHTTPHeaderFields`
3951
/// - All header's from `additionalHeaders` added to `allHTTPHeaderFields`
4052
/// - If the `context` conforms to `RequestContextTimeoutConfigurable`, the `timeoutInterval` is
4153
/// set to the context's `requestTimeout`.
4254
///
55+
/// - Note: This should be called within the implementation of `toURLRequest()` and the returned request
56+
/// can then be modified as necessary before being returned.
57+
///
4358
/// - Returns: A `URLRequest` configured as described above.
4459
public func createDefaultRequest() -> URLRequest {
4560
var request = URLRequest(url: self.graphQLEndpoint)
4661

4762
request.httpMethod = GraphQLHTTPMethod.POST.rawValue
4863

49-
for (fieldName, value) in additionalHeaders {
64+
clientAwarenessMetadata.applyHeaders(to: &request)
65+
for (fieldName, value) in self.additionalHeaders {
5066
request.addValue(value, forHTTPHeaderField: fieldName)
5167
}
5268

53-
if let configContext = context as? any RequestContextTimeoutConfigurable {
69+
if let configContext = self.context as? any RequestContextTimeoutConfigurable {
5470
request.timeoutInterval = configContext.requestTimeout
5571
}
5672

@@ -65,62 +81,4 @@ extension GraphQLRequest {
6581
self.additionalHeaders.merge(headers) { (_, new) in new }
6682
}
6783

68-
/// A helper method that dds the Apollo client headers to the given request
69-
/// These header values are used for telemetry to track the source of client requests.
70-
///
71-
/// This should be called during setup of any implementation of `GraphQLRequest` to provide these
72-
/// header values.
73-
///
74-
/// - Parameters:
75-
/// - clientName: The client name. Defaults to the application's bundle identifier + "-apollo-ios".
76-
/// - clientVersion: The client version. Defaults to the bundle's short version or build number.
77-
public mutating func addApolloClientHeaders(
78-
clientName: String? = Self.defaultClientName,
79-
clientVersion: String? = Self.defaultClientVersion
80-
) {
81-
additionalHeaders[Self.headerFieldNameApolloClientName] = clientName
82-
additionalHeaders[Self.headerFieldNameApolloClientVersion] = clientVersion
83-
}
84-
85-
/// The field name for the Apollo Client Name header
86-
static var headerFieldNameApolloClientName: String {
87-
return "apollographql-client-name"
88-
}
89-
90-
/// The field name for the Apollo Client Version header
91-
static var headerFieldNameApolloClientVersion: String {
92-
return "apollographql-client-version"
93-
}
94-
95-
/// The default client name to use when setting up the `clientName` property
96-
public static var defaultClientName: String {
97-
guard let identifier = Bundle.main.bundleIdentifier else {
98-
return "apollo-ios-client"
99-
}
100-
101-
return "\(identifier)-apollo-ios"
102-
}
103-
104-
/// The default client version to use when setting up the `clientVersion` property.
105-
public static var defaultClientVersion: String {
106-
var version = String()
107-
if let shortVersion = Bundle.main.shortVersion {
108-
version.append(shortVersion)
109-
}
110-
111-
if let buildNumber = Bundle.main.buildNumber {
112-
if version.isEmpty {
113-
version.append(buildNumber)
114-
} else {
115-
version.append("-\(buildNumber)")
116-
}
117-
}
118-
119-
if version.isEmpty {
120-
version = "(unknown)"
121-
}
122-
123-
return version
124-
}
125-
12684
}

apollo-ios/Sources/Apollo/JSONRequest.swift

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ public struct JSONRequest<Operation: GraphQLOperation>: GraphQLRequest, AutoPers
3838
/// Mutation operations always use POST, even when this is `false`
3939
public let useGETForQueries: Bool
4040

41-
private let sendEnhancedClientAwareness: Bool
41+
/// The telemetry metadata about the client. This is used by GraphOS Studio's
42+
/// [client awareness](https://www.apollographql.com/docs/graphos/platform/insights/client-segmentation)
43+
/// feature.
44+
public var clientAwarenessMetadata: ClientAwarenessMetadata
4245

4346
/// Designated initializer
4447
///
@@ -59,14 +62,12 @@ public struct JSONRequest<Operation: GraphQLOperation>: GraphQLRequest, AutoPers
5962
operation: Operation,
6063
graphQLEndpoint: URL,
6164
contextIdentifier: UUID? = nil,
62-
clientName: String? = Self.defaultClientName,
63-
clientVersion: String? = Self.defaultClientVersion,
6465
cachePolicy: CachePolicy = .default,
6566
context: (any RequestContext)? = nil,
6667
apqConfig: AutoPersistedQueryConfiguration = .init(),
6768
useGETForQueries: Bool = false,
6869
requestBodyCreator: any JSONRequestBodyCreator = DefaultRequestBodyCreator(),
69-
sendEnhancedClientAwareness: Bool = true
70+
clientAwarenessMetadata: ClientAwarenessMetadata = ClientAwarenessMetadata()
7071
) {
7172
self.operation = operation
7273
self.graphQLEndpoint = graphQLEndpoint
@@ -77,20 +78,13 @@ public struct JSONRequest<Operation: GraphQLOperation>: GraphQLRequest, AutoPers
7778

7879
self.apqConfig = apqConfig
7980
self.useGETForQueries = useGETForQueries
80-
self.sendEnhancedClientAwareness = sendEnhancedClientAwareness
81+
self.clientAwarenessMetadata = clientAwarenessMetadata
8182

82-
self.setupDefaultHeaders(
83-
clientName: clientName,
84-
clientVersion: clientVersion
85-
)
83+
self.setupDefaultHeaders()
8684
}
8785

88-
private mutating func setupDefaultHeaders(
89-
clientName: String? = Self.defaultClientName,
90-
clientVersion: String? = Self.defaultClientVersion
91-
) {
92-
self.addHeader(name: "Content-Type", value: "application/json")
93-
self.addApolloClientHeaders(clientName: clientName, clientVersion: clientVersion)
86+
private mutating func setupDefaultHeaders() {
87+
self.addHeader(name: "Content-Type", value: "application/json")
9488

9589
if Operation.operationType == .subscription {
9690
self.addHeader(
@@ -181,11 +175,7 @@ public struct JSONRequest<Operation: GraphQLOperation>: GraphQLRequest, AutoPers
181175
for: self,
182176
sendQueryDocument: sendQueryDocument,
183177
autoPersistQuery: autoPersistQueries
184-
)
185-
186-
if self.sendEnhancedClientAwareness {
187-
addEnhancedClientAwarenessExtension(to: &body)
188-
}
178+
)
189179

190180
return body
191181
}

0 commit comments

Comments
 (0)