diff --git a/Purchases/Misc/RCCrossPlatformSupport.h b/Purchases/Misc/RCCrossPlatformSupport.h index 89482cc062..9669c94e83 100644 --- a/Purchases/Misc/RCCrossPlatformSupport.h +++ b/Purchases/Misc/RCCrossPlatformSupport.h @@ -46,6 +46,15 @@ #define UI_DEVICE_AVAILABLE 0 #endif +// Should match available platforms in +// https://developer.apple.com/documentation/watchkit/wkinterfacedevice?language=objc +#if TARGET_OS_WATCH +#define WKINTERFACE_DEVICE_AVAILABLE 1 +#else +#define WKINTERFACE_DEVICE_AVAILABLE 0 +#endif + + // Should match available platforms in // https://developer.apple.com/documentation/iad/adclient?language=objc #if TARGET_OS_IOS diff --git a/Purchases/Misc/RCSystemInfo.h b/Purchases/Misc/RCSystemInfo.h index f34d40fd28..d7774c98c6 100644 --- a/Purchases/Misc/RCSystemInfo.h +++ b/Purchases/Misc/RCSystemInfo.h @@ -26,7 +26,9 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)frameworkVersion; + (NSString *)systemVersion; + (NSString *)appVersion; ++ (NSString *)buildVersion; + (NSString *)platformHeader; ++ (nullable NSString *)identifierForVendor; + (NSURL *)serverHostURL; + (nullable NSURL *)proxyURL; diff --git a/Purchases/Misc/RCSystemInfo.m b/Purchases/Misc/RCSystemInfo.m index c4348227a3..8a658baeea 100644 --- a/Purchases/Misc/RCSystemInfo.m +++ b/Purchases/Misc/RCSystemInfo.m @@ -62,10 +62,24 @@ + (NSString *)appVersion { return version ?: @""; } ++ (NSString *)buildVersion { + NSString *version = NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]; + return version ?: @""; +} + + (NSString *)platformHeader { return self.forceUniversalAppStore ? @"iOS" : PLATFORM_HEADER; } ++ (nullable NSString *)identifierForVendor { +#if UI_DEVICE_AVAILABLE + return UIDevice.currentDevice.identifierForVendor.UUIDString; +#elif WKINTERFACE_DEVICE_AVAILABLE + return WKInterfaceDevice.currentDevice.identifierForVendor.UUIDString; +#endif + return nil; +} + + (NSURL *)defaultServerHostURL { return [NSURL URLWithString:defaultServerHostName]; } diff --git a/Purchases/Networking/RCHTTPClient.m b/Purchases/Networking/RCHTTPClient.m index e703398f5a..63a8b12eca 100644 --- a/Purchases/Networking/RCHTTPClient.m +++ b/Purchases/Networking/RCHTTPClient.m @@ -201,6 +201,7 @@ - (NSDictionary *)defaultHeaders { @"X-Platform-Version": RCSystemInfo.systemVersion, @"X-Platform-Flavor": self.systemInfo.platformFlavor, @"X-Client-Version": RCSystemInfo.appVersion, + @"X-Client-Build-Version": RCSystemInfo.buildVersion, @"X-Observer-Mode-Enabled": observerMode }]; @@ -209,6 +210,11 @@ - (NSDictionary *)defaultHeaders { headers[@"X-Platform-Flavor-Version"] = platformFlavorVersion; } + NSString * _Nullable idfv = RCSystemInfo.identifierForVendor; + if (idfv) { + headers[@"X-Apple-Device-Identifier"] = idfv; + } + return headers; } diff --git a/PurchasesTests/Networking/HTTPClientTests.swift b/PurchasesTests/Networking/HTTPClientTests.swift index d47efb3ba9..5f17cf9429 100644 --- a/PurchasesTests/Networking/HTTPClientTests.swift +++ b/PurchasesTests/Networking/HTTPClientTests.swift @@ -319,6 +319,40 @@ class HTTPClientTests: XCTestCase { expect(headerPresent).toEventually(equal(true)) } + func testAlwaysPassesClientBuildVersion() { + let path = "/a_random_path" + var headerPresent = false + + let version = Bundle.main.infoDictionary!["CFBundleVersion"] as! String + + stub(condition: hasHeaderNamed("X-Client-Build-Version", value: version )) { request in + headerPresent = true + return HTTPStubsResponse(data: Data.init(), statusCode:200, headers:nil) + } + + self.client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), + headers: ["test_header": "value"], completionHandler:nil) + + expect(headerPresent).toEventually(equal(true)) + } + + func testAlwaysPassesAppleDeviceIdentifier() { + let path = "/a_random_path" + var headerPresent = false + + let idfv = UIDevice.current.identifierForVendor!.uuidString + + stub(condition: hasHeaderNamed("X-Apple-Device-Identifier", value: idfv )) { request in + headerPresent = true + return HTTPStubsResponse(data: Data.init(), statusCode:200, headers:nil) + } + + self.client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), + headers: ["test_header": "value"], completionHandler:nil) + + expect(headerPresent).toEventually(equal(true)) + } + func testDefaultsPlatformFlavorToNative() { let path = "/a_random_path" var headerPresent = false