Skip to content

Commit

Permalink
WIP implement ClientLibraryInfo class
Browse files Browse the repository at this point in the history
ably/specification#113

TODO test

TODO mention spec points

TODO we need docstrings, what's the process for that? Asked Srushtika
here:
https://ably-real-time.slack.com/archives/C030C5YLY/p1668457571004079

Closes #1530.
  • Loading branch information
lawrence-forooghian committed Nov 14, 2022
1 parent c36947c commit 5802fb9
Show file tree
Hide file tree
Showing 18 changed files with 226 additions and 107 deletions.
40 changes: 32 additions & 8 deletions Ably.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Scripts/set-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ git add Version.xcconfig
other_files=(
"README.md"
"Scripts/jazzy.sh"
"Source/ARTDefault.m"
"Source/ARTClientLibraryInfo.m"
"Spec/Tests/ARTDefaultTests.swift"
"Spec/Tests/ClientLibraryInfoTests.swift"
"Spec/Tests/RealtimeClientConnectionTests.swift"
"Spec/Tests/RestClientTests.swift"
)
Expand Down
10 changes: 10 additions & 0 deletions Source/ARTClientLibraryInfo+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import <Ably/ARTClientLibraryInfo.h>

extern NSString *const ARTClientLibraryInfo_libraryVersion;

@interface ARTClientLibraryInfo (Private)

+ (NSString *)libraryAgentIdentifier;
+ (NSString *)platformAgentIdentifier;

@end
13 changes: 13 additions & 0 deletions Source/ARTClientLibraryInfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ARTClientLibraryInfo : NSObject

// TODO mention the no version sentinel
@property (class, readonly) NSDictionary<NSString *, NSString *> *agents;
+ (NSString *)agentIdentifierWithAdditionalAgents:(nullable NSDictionary<NSString *, NSString *> *)additionalAgents;

@end

NS_ASSUME_NONNULL_END
113 changes: 113 additions & 0 deletions Source/ARTClientLibraryInfo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#import "ARTClientLibraryInfo.h"
#import "ARTClientLibraryInfo+Private.h"
#import "ARTDefault.h"
#import "ARTDefault+Private.h"
#import "ARTClientOptions.h"
#import <sys/utsname.h>

NSString *const ARTClientLibraryInfo_libraryVersion = @"1.2.16";
static NSString *const _libraryName = @"ably-cocoa";

// NSOperatingSystemVersion has NSInteger as version components for some reason, so mitigate it here.
static inline UInt32 conformVersionComponent(const NSInteger component) {
return (component < 0) ? 0 : (UInt32)component;
}

@implementation ARTClientLibraryInfo

+ (NSDictionary<NSString *, NSString *> *)agents {
NSMutableDictionary<NSString *, NSString *> *result = [NSMutableDictionary dictionary];

[result addEntriesFromDictionary:[self platformAgent]];
[result addEntriesFromDictionary:[self libraryAgent]];

return result;
}

+ (NSString *)agentIdentifierWithAdditionalAgents:(nullable NSDictionary<NSString *, NSString *> *)additionalAgents {
NSMutableDictionary<NSString *, NSString *> *agents = [self.agents mutableCopy];

for (NSString *const additionalAgentName in additionalAgents) {
agents[additionalAgentName] = additionalAgents[additionalAgentName];
}

return [self agentIdentifierForAgents:agents];
}

+ (NSString *)agentIdentifierForAgents:(NSDictionary<NSString *, NSString*> *)agents {
NSMutableString *agentIdentifier = [NSMutableString string];

// We sort the agent names so that we have a predictable order when testing.
NSArray<NSString *> *sortedAgentNames = [agents.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSString *name in sortedAgentNames) {
NSString *const version = agents[name];
if (version == ARTClientOptionsAgentNotVersioned) {
[agentIdentifier appendFormat:@"%@ ", name];
} else {
[agentIdentifier appendFormat:@"%@/%@ ", name, version];
}
}

return agentIdentifier;
}

+ (NSDictionary<NSString *, NSString *> *)libraryAgent {
return @{ _libraryName: ARTClientLibraryInfo_libraryVersion };
}

+ (NSString *)libraryAgentIdentifier {
return [self agentIdentifierForAgents:[self libraryAgent]];
}

+ (NSDictionary<NSString *, NSString *> *)platformAgent {
NSString *const osName = [self osName];

if (osName == nil) {
return @{};
}

return @{ osName: [self osVersionString] };
}

+ (NSString *)platformAgentIdentifier {
return [self agentIdentifierForAgents:[self platformAgent]];
}

+ (NSString *)osName {
return
#if TARGET_OS_IOS
@"iOS"
#elif TARGET_OS_TV
@"tvOS"
#elif TARGET_OS_WATCH
@"watchOS"
#elif TARGET_OS_OSX
@"macOS"
#else
nil
#endif
;
}

+ (NSString *)osVersionString {
static NSString *versionString;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
versionString = [NSString stringWithFormat:@"%lu.%lu.%lu",
(unsigned long)conformVersionComponent(version.majorVersion),
(unsigned long)conformVersionComponent(version.minorVersion),
(unsigned long)conformVersionComponent(version.patchVersion)];
});
return versionString;
}

+ (NSString *)deviceModel {
struct utsname systemInfo;
if (uname(&systemInfo) < 0) {
return nil;
}
return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}

@end
1 change: 0 additions & 1 deletion Source/ARTClientOptions+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@
+ (void)setDefaultEnvironment:(NSString *_Nullable)environment;
+ (BOOL)getDefaultIdempotentRestPublishingForVersion:(NSString *_Nonnull)version;
- (NSURLComponents *_Nonnull)restUrlComponents;
- (NSString *_Nonnull)agentLibraryIdentifier;

@end
1 change: 1 addition & 0 deletions Source/ARTClientOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

NS_ASSUME_NONNULL_BEGIN

// TODO correct place for this?
/**
* Use this pointer as a dictionary value in the `ARTClientOptions.agents` property to indicate that an agent does not have a version.
*/
Expand Down
20 changes: 0 additions & 20 deletions Source/ARTClientOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -222,24 +222,4 @@ - (NSString *)host:(NSString *)host forEnvironment:(NSString *)environment {
return [NSString stringWithFormat:@"%@-%@", environment, host];
}

- (NSString *)agentLibraryIdentifier {
NSMutableString *agents = [NSMutableString string];
[agents appendFormat:@"%@ ", [ARTDefault libraryAgent]];

// We sort the agent names so that we have a predictable order when testing.
NSArray<NSString *> *sortedAgentNames = [self.agents.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSString *name in sortedAgentNames) {
NSString *const version = self.agents[name];
if (version == ARTClientOptionsAgentNotVersioned) {
[agents appendFormat:@"%@ ", name];
} else {
[agents appendFormat:@"%@/%@ ", name, version];
}
}

[agents appendFormat:@"%@", [ARTDefault platformAgent]];

return agents;
}

@end
58 changes: 4 additions & 54 deletions Source/ARTDefault.m
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
#import "Ably.h"
#import "ARTDefault+Private.h"
#import "ARTNSArray+ARTFunctional.h"
#import <sys/utsname.h>

// NSOperatingSystemVersion has NSInteger as version components for some reason, so mitigate it here.
static inline UInt32 conformVersionComponent(const NSInteger component) {
return (component < 0) ? 0 : (UInt32)component;
}
#import "ARTClientLibraryInfo+Private.h"

static NSString *const ARTDefault_apiVersion = @"1.2";
static NSString *const ARTDefault_libraryVersion = @"1.2.16";

NSString *const ARTDefaultProduction = @"production";

static NSString *const ARTDefault_restHost = @"rest.ably.io";
static NSString *const ARTDefault_realtimeHost = @"realtime.ably.io";
static NSString *const ARTDefault_libraryName = @"ably-cocoa";

static NSTimeInterval _realtimeRequestTimeout = 10.0;
static NSTimeInterval _connectionStateTtl = 60.0;
Expand All @@ -28,7 +21,7 @@ + (NSString *)apiVersion {
}

+ (NSString *)libraryVersion {
return ARTDefault_libraryVersion;
return ARTClientLibraryInfo_libraryVersion;
}

+ (NSArray*)fallbackHostsWithEnvironment:(NSString *)environment {
Expand Down Expand Up @@ -99,55 +92,12 @@ + (void)setMaxMessageSize:(NSInteger)value {
}
}

+ (NSString *)osName {
return
#if TARGET_OS_IOS
@"iOS"
#elif TARGET_OS_TV
@"tvOS"
#elif TARGET_OS_WATCH
@"watchOS"
#elif TARGET_OS_OSX
@"macOS"
#else
nil
#endif
;
}

+ (NSString *)osVersionString {
static NSString *versionString;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
versionString = [NSString stringWithFormat:@"%lu.%lu.%lu",
(unsigned long)conformVersionComponent(version.majorVersion),
(unsigned long)conformVersionComponent(version.minorVersion),
(unsigned long)conformVersionComponent(version.patchVersion)];
});
return versionString;
}

+ (NSString *)deviceModel {
struct utsname systemInfo;
if (uname(&systemInfo) < 0) {
return nil;
}
return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}

+ (NSString *)libraryAgent {
NSMutableString *agent = [NSMutableString stringWithFormat:@"%@/%@", ARTDefault_libraryName, ARTDefault_libraryVersion];
return agent;
return [ARTClientLibraryInfo libraryAgentIdentifier];
}

+ (NSString *)platformAgent {
NSMutableString *agent = [NSMutableString string];
NSString *osName = [self osName];
if (osName != nil) {
[agent appendFormat:@"%@/%@", osName, [self osVersionString]];
}
return agent;
return [ARTClientLibraryInfo platformAgentIdentifier];
}

@end
3 changes: 2 additions & 1 deletion Source/ARTRest.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#import "ARTNSMutableURLRequest+ARTUtils.h"
#import "ARTNSURL+ARTUtils.h"
#import "ARTTime.h"
#import "ARTClientLibraryInfo.h"

@implementation ARTRest {
ARTQueuedDealloc *_dealloc;
Expand Down Expand Up @@ -313,7 +314,7 @@ - (NSString *)description {
[mutableRequest setAcceptHeader:self.defaultEncoder encoders:self.encoders];
[mutableRequest setTimeoutInterval:_options.httpRequestTimeout];
[mutableRequest setValue:[ARTDefault apiVersion] forHTTPHeaderField:@"X-Ably-Version"];
[mutableRequest setValue:[_options agentLibraryIdentifier] forHTTPHeaderField:@"Ably-Agent"];
[mutableRequest setValue:[ARTClientLibraryInfo agentIdentifierWithAdditionalAgents:_options.agents] forHTTPHeaderField:@"Ably-Agent"];
if (_options.clientId && !self.auth.isTokenAuth) {
[mutableRequest setValue:encodeBase64(_options.clientId) forHTTPHeaderField:@"X-Ably-ClientId"];
}
Expand Down
3 changes: 2 additions & 1 deletion Source/ARTWebSocketTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import "NSURLQueryItem+Stringifiable.h"
#import "ARTNSMutableDictionary+ARTDictionaryUtil.h"
#import "ARTStringifiable.h"
#import "ARTClientLibraryInfo.h"

enum {
ARTWsNeverConnected = -1,
Expand Down Expand Up @@ -178,7 +179,7 @@ - (NSURL *)setupWebSocket:(NSDictionary<NSString *, NSURLQueryItem *> *)params w
[queryItems addValueAsURLQueryItem:[ARTDefault apiVersion] forKey:@"v"];

// Lib
[queryItems addValueAsURLQueryItem:[options agentLibraryIdentifier] forKey:@"agent"];
[queryItems addValueAsURLQueryItem:[ARTClientLibraryInfo agentIdentifierWithAdditionalAgents:options.agents] forKey:@"agent"];

// Transport Params
if (options.transportParams != nil) {
Expand Down
1 change: 1 addition & 0 deletions Source/Ably.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[];
#import <Ably/CompatibilityMacros.h>
#import <Ably/ARTPendingMessage.h>
#import <Ably/ARTStringifiable.h>
#import <Ably/ARTClientLibraryInfo.h>
1 change: 1 addition & 0 deletions Source/PrivateHeaders/Ably/ARTClientLibraryInfo+Private.h
1 change: 1 addition & 0 deletions Source/include/Ably/ARTClientLibraryInfo.h
2 changes: 1 addition & 1 deletion Spec/Test Utilities/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1820,7 +1820,7 @@ extension HTTPURLResponse {
- Warning: Don't use 'allHeaderFields' property. See discussion.
*/
@available(*, deprecated, message: "Don't use 'allHeaderFields'. It's not case-insensitive. Please use 'value(forHTTPHeaderField:)' method")
open var _allHeaderFields: [AnyHashable : Any] { return [:] }
public var _allHeaderFields: [AnyHashable : Any] { return [:] }

/**
The value which corresponds to the given header
Expand Down
42 changes: 42 additions & 0 deletions Spec/Tests/ClientLibraryInfoTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import XCTest
import Ably

final class ClientLibraryInfoTests: XCTestCase {

func testAgents() {
let agents = ARTClientLibraryInfo.agents

XCTAssertEqual(agents.keys.count, 2)

XCTAssertEqual(agents["ably-cocoa"], "1.2.16")

#if os(iOS)
XCTAssertTrue(agents.keys.contains("iOS"))
#elseif os(tvOS)
XCTAssertTrue(agents.keys.contains("tvOS"))
#elseif os(watchOS)
XCTAssertTrue(agents.keys.contains("watchOS"))
#elseif os(macOS)
XCTAssertTrue(agents.keys.contains("macOS"))
#else
#error("Building for unknown OS")
#endif
}

func testAgentIdentifierWithAdditionalAgents_withNilAdditionalAgents() {
let expectedIdentifier = "\(ARTDefault.libraryAgent()) \(ARTDefault.platformAgent())"

XCTAssertEqual(ARTClientLibraryInfo.agentIdentifier(withAdditionalAgents: nil), expectedIdentifier)
}

func testAgentIdentifierWithAdditionalAgents_withNonNilAdditionalAgents() {
let additionalAgents = [
"demolib": "0.0.1",
"morelib": ARTClientOptionsAgentNotVersioned
]

let expectedIdentifier = "\(ARTDefault.libraryAgent()) demolib/0.0.1 morelib \(ARTDefault.platformAgent())"

XCTAssertEqual(ARTClientLibraryInfo.agentIdentifier(withAdditionalAgents: additionalAgents), expectedIdentifier)
}
}
19 changes: 0 additions & 19 deletions Spec/Tests/ClientOptionsTests.swift

This file was deleted.

Loading

0 comments on commit 5802fb9

Please sign in to comment.