diff --git a/README.md b/README.md index 1eaea9a..b72bbdc 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,9 @@ The ready event is fired once the client has received it's first set of feature Note: To release the package you'll need to have [CocoaPods](https://cocoapods.org/) installed. -First, you'll need to add a tag. Releasing the tag is enough for the Swift package manager, but it's polite to also ensure CocoaPods users can also consume the code. +Update `Sources/Version/Version.swift` with the new version number. It will be used in `x-unleash-sdk` header as a version reported to Unleash server. + +Then, you'll need to add a tag with the same version number as the previous step. Releasing the tag is enough for the Swift package manager, but it's polite to also ensure CocoaPods users can also consume the code. ```sh git tag -a 0.0.4 -m "v0.0.4" diff --git a/Sources/UnleashProxyClientSwift/Client/UnleashProxyClientSwift.swift b/Sources/UnleashProxyClientSwift/Client/UnleashProxyClientSwift.swift index a962d54..dd1c80b 100644 --- a/Sources/UnleashProxyClientSwift/Client/UnleashProxyClientSwift.swift +++ b/Sources/UnleashProxyClientSwift/Client/UnleashProxyClientSwift.swift @@ -7,6 +7,7 @@ public class UnleashClientBase { var timer: Timer? var poller: Poller var metrics: Metrics + var connectionId: UUID public init( unleashUrl: String, @@ -26,6 +27,7 @@ public class UnleashClientBase { fatalError("Invalid Unleash URL: \(unleashUrl)") } + self.connectionId = UUID() self.timer = nil if let poller = poller { self.poller = poller @@ -35,7 +37,9 @@ public class UnleashClientBase { unleashUrl: url, apiKey: clientKey, customHeaders: customHeaders, - bootstrap: bootstrap + bootstrap: bootstrap, + appName: appName, + connectionId: connectionId ) } if let metrics = metrics { @@ -51,7 +55,7 @@ public class UnleashClientBase { } task.resume() } - self.metrics = Metrics(appName: appName, metricsInterval: Double(metricsInterval), clock: { return Date() }, disableMetrics: disableMetrics, poster: urlSessionPoster, url: url, clientKey: clientKey, customHeaders: customHeaders) + self.metrics = Metrics(appName: appName, metricsInterval: Double(metricsInterval), clock: { return Date() }, disableMetrics: disableMetrics, poster: urlSessionPoster, url: url, clientKey: clientKey, customHeaders: customHeaders, connectionId: connectionId) } self.context = Context(appName: appName, environment: environment) diff --git a/Sources/UnleashProxyClientSwift/Metrics/Metrics.swift b/Sources/UnleashProxyClientSwift/Metrics/Metrics.swift index 3d2028d..d9d43d9 100644 --- a/Sources/UnleashProxyClientSwift/Metrics/Metrics.swift +++ b/Sources/UnleashProxyClientSwift/Metrics/Metrics.swift @@ -13,6 +13,7 @@ public class Metrics { var bucket: Bucket let url: URL let customHeaders: [String: String] + let connectionId: UUID init(appName: String, metricsInterval: TimeInterval, @@ -21,7 +22,8 @@ public class Metrics { poster: @escaping PosterHandler, url: URL, clientKey: String, - customHeaders: [String: String] = [:]) { + customHeaders: [String: String] = [:], + connectionId: UUID) { self.appName = appName self.metricsInterval = metricsInterval self.clock = clock @@ -31,6 +33,7 @@ public class Metrics { self.clientKey = clientKey self.bucket = Bucket(clock: clock) self.customHeaders = customHeaders + self.connectionId = connectionId } func start() { @@ -105,6 +108,9 @@ public class Metrics { request.addValue("no-cache", forHTTPHeaderField: "Cache") request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue(clientKey, forHTTPHeaderField: "Authorization") + request.addValue(appName, forHTTPHeaderField: "x-unleash-appname") + request.addValue(connectionId.uuidString, forHTTPHeaderField: "x-unleash-connection-id") + request.setValue("unleash-client-swift:\(LibraryInfo.version)", forHTTPHeaderField: "x-unleash-sdk") if !self.customHeaders.isEmpty { for (key, value) in self.customHeaders { request.setValue(value, forHTTPHeaderField: key) diff --git a/Sources/UnleashProxyClientSwift/Poller/Poller.swift b/Sources/UnleashProxyClientSwift/Poller/Poller.swift index 8ac48e8..015bdc9 100644 --- a/Sources/UnleashProxyClientSwift/Poller/Poller.swift +++ b/Sources/UnleashProxyClientSwift/Poller/Poller.swift @@ -9,7 +9,9 @@ public class Poller { var ready: Bool var apiKey: String; var etag: String; - + var appName: String; + var connectionId: UUID; + private let session: PollerSession var storageProvider: StorageProvider let customHeaders: [String: String] @@ -21,11 +23,15 @@ public class Poller { session: PollerSession = URLSession.shared, storageProvider: StorageProvider = DictionaryStorageProvider(), customHeaders: [String: String] = [:], - bootstrap: Bootstrap = .toggles([]) + bootstrap: Bootstrap = .toggles([]), + appName: String, + connectionId: UUID ) { self.refreshInterval = refreshInterval self.unleashUrl = unleashUrl self.apiKey = apiKey + self.appName = appName + self.connectionId = connectionId self.timer = nil self.ready = false self.etag = "" @@ -107,6 +113,9 @@ public class Poller { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(self.apiKey, forHTTPHeaderField: "Authorization") request.setValue(self.etag, forHTTPHeaderField: "If-None-Match") + request.setValue(self.appName, forHTTPHeaderField: "x-unleash-appname") + request.setValue(self.connectionId.uuidString, forHTTPHeaderField: "x-unleash-connection-id") + request.setValue("unleash-client-swift:\(LibraryInfo.version)", forHTTPHeaderField: "x-unleash-sdk") if !self.customHeaders.isEmpty { for (key, value) in self.customHeaders { request.setValue(value, forHTTPHeaderField: key) diff --git a/Sources/UnleashProxyClientSwift/Version/Version.swift b/Sources/UnleashProxyClientSwift/Version/Version.swift new file mode 100644 index 0000000..29ff3a5 --- /dev/null +++ b/Sources/UnleashProxyClientSwift/Version/Version.swift @@ -0,0 +1,3 @@ +public struct LibraryInfo { + public static let version = "1.6.0" // Update this string with each new release +} \ No newline at end of file diff --git a/Tests/UnleashProxyClientSwiftTests/MetricsTests.swift b/Tests/UnleashProxyClientSwiftTests/MetricsTests.swift index 468ba94..38d58c3 100644 --- a/Tests/UnleashProxyClientSwiftTests/MetricsTests.swift +++ b/Tests/UnleashProxyClientSwiftTests/MetricsTests.swift @@ -26,7 +26,8 @@ final class MetricsTests: XCTestCase { clock: fixedClock, poster: poster, url: URL(string: "https://unleashinstance.com")!, - clientKey: "testKey") + clientKey: "testKey", + connectionId: UUID()) metrics.start() metrics.count(name: "testToggle", enabled: true) @@ -84,7 +85,8 @@ final class MetricsTests: XCTestCase { clock: fixedClock, poster: poster, url: URL(string: "https://unleashinstance.com")!, - clientKey: "testKey") + clientKey: "testKey", + connectionId: UUID()) metrics.start() metrics.count(name: "irrelevant", enabled: true) @@ -105,7 +107,8 @@ final class MetricsTests: XCTestCase { disableMetrics: true, poster: poster, url: URL(string: "https://unleashinstance.com")!, - clientKey: "testKey") + clientKey: "testKey", + connectionId: UUID()) metrics.start() metrics.count(name: "irrelevant", enabled: true) diff --git a/Tests/UnleashProxyClientSwiftTests/MockMetrics.swift b/Tests/UnleashProxyClientSwiftTests/MockMetrics.swift index 9534889..9f41722 100644 --- a/Tests/UnleashProxyClientSwiftTests/MockMetrics.swift +++ b/Tests/UnleashProxyClientSwiftTests/MockMetrics.swift @@ -13,6 +13,6 @@ class MockMetrics: Metrics { let response = HTTPURLResponse(url: URL(string: "https://irrelevant.com")!, statusCode: 200, httpVersion: nil, headerFields: nil) completionHandler(.success((Data(), response!))) } - super.init(appName: appName, metricsInterval: Double(15), clock: { return Date() }, disableMetrics: false, poster: noOpPoster, url: URL(string: "https://irrelevant.com")!, clientKey: "irrelevant") + super.init(appName: appName, metricsInterval: Double(15), clock: { return Date() }, disableMetrics: false, poster: noOpPoster, url: URL(string: "https://irrelevant.com")!, clientKey: "irrelevant", connectionId: UUID()) } } diff --git a/Tests/UnleashProxyClientSwiftTests/MockPoller.swift b/Tests/UnleashProxyClientSwiftTests/MockPoller.swift index 1ddf03b..6de06d0 100644 --- a/Tests/UnleashProxyClientSwiftTests/MockPoller.swift +++ b/Tests/UnleashProxyClientSwiftTests/MockPoller.swift @@ -51,9 +51,9 @@ class MockPoller: Poller { var dataGenerator: () -> [String: Toggle]; var stubCompletionError: PollerError? - init(callback: @escaping () -> [String: Toggle], unleashUrl: URL, apiKey: String, session: PollerSession) { + init(callback: @escaping () -> [String: Toggle], unleashUrl: URL, apiKey: String, session: PollerSession, appName: String, connectionId: UUID) { self.dataGenerator = callback - super.init(refreshInterval: 15, unleashUrl: unleashUrl, apiKey: apiKey, session: session) + super.init(refreshInterval: 15, unleashUrl: unleashUrl, apiKey: apiKey, session: session, appName: appName, connectionId: connectionId) } override func getFeatures(context: Context, completionHandler: ((PollerError?) -> Void)? = nil) -> Void { diff --git a/Tests/UnleashProxyClientSwiftTests/PollerTests.swift b/Tests/UnleashProxyClientSwiftTests/PollerTests.swift index 696e9c0..d4d2a65 100644 --- a/Tests/UnleashProxyClientSwiftTests/PollerTests.swift +++ b/Tests/UnleashProxyClientSwiftTests/PollerTests.swift @@ -5,6 +5,8 @@ final class PollerTests: XCTestCase { private let unleashUrl = URL(string: "https://app.unleash-hosted.com/hosted/api/proxy")! private let apiKey = "SECRET" + private let appName = "APPNAME" + private let connectionId = UUID(uuidString: "123E4567-E89B-12d3-A456-426614174000")! private let timeout = 1.0 func testWhenInitWithBootstrapTogglesThenAddToStore() { @@ -190,13 +192,16 @@ final class PollerTests: XCTestCase { let response = mockResponse() let data = stubData() let session = MockPollerSession(data: data, response: response) - let poller = Poller(refreshInterval: nil, unleashUrl: unleashUrl, apiKey: apiKey, session: session, customHeaders: customHeaders) + let poller = Poller(refreshInterval: nil, unleashUrl: unleashUrl, apiKey: apiKey, session: session, customHeaders: customHeaders, appName: appName, connectionId: connectionId) let expectation = XCTestExpectation(description: "Expect custom headers to be set in the request.") session.performRequestHandler = { request in XCTAssertEqual(request.value(forHTTPHeaderField: "X-Custom-Header"), "CustomValue") XCTAssertEqual(request.value(forHTTPHeaderField: "X-Another-Header"), "AnotherValue") + XCTAssertEqual(request.value(forHTTPHeaderField: "x-unleash-appname"), "APPNAME") + XCTAssertEqual(request.value(forHTTPHeaderField: "x-unleash-connection-id"), "123E4567-E89B-12D3-A456-426614174000") + XCTAssertTrue(request.value(forHTTPHeaderField: "x-unleash-sdk")!.range(of: #"^unleash-client-swift:\d+\.\d+\.\d+$"#, options: .regularExpression) != nil, "x-unleash-sdk header sdk:semver format does not match") expectation.fulfill() } @@ -251,7 +256,9 @@ final class PollerTests: XCTestCase { unleashUrl: url ?? unleashUrl, apiKey: apiKey, session: session, - bootstrap: bootstrap + bootstrap: bootstrap, + appName: appName, + connectionId: connectionId ) } diff --git a/Tests/UnleashProxyClientSwiftTests/testUtils.swift b/Tests/UnleashProxyClientSwiftTests/testUtils.swift index 8dc9c79..f1579e7 100644 --- a/Tests/UnleashProxyClientSwiftTests/testUtils.swift +++ b/Tests/UnleashProxyClientSwiftTests/testUtils.swift @@ -50,7 +50,9 @@ func setup( callback: dataGenerator, unleashUrl: URL(string: "https://app.unleash-hosted.com/hosted/api/proxy")!, apiKey: "SECRET", - session: session + session: session, + appName: "APPNAME", + connectionId: UUID() ) let metrics = MockMetrics(appName: "test") @@ -80,7 +82,9 @@ func setup( callback: dataGenerator, unleashUrl: URL(string: "https://app.unleash-hosted.com/hosted/api/proxy")!, apiKey: "SECRET", - session: session + session: session, + appName: "APPNAME", + connectionId: UUID() ) let metrics = MockMetrics(appName: "test") @@ -107,7 +111,9 @@ func setupBase( callback: dataGenerator, unleashUrl: URL(string: "https://app.unleash-hosted.com/hosted/api/proxy")!, apiKey: "SECRET", - session: session + session: session, + appName: "APPNAME", + connectionId: UUID() ) let metrics = MockMetrics(appName: "test")