From 17ffc25c1ac131f5732bd1abf5abbfe7d5a0f48d Mon Sep 17 00:00:00 2001 From: Phoenix Saha Date: Thu, 14 Sep 2023 16:33:06 -0700 Subject: [PATCH] Add support for watchOS --- Segment.xcodeproj/project.pbxproj | 6 +++ Segment/Classes/SEGAnalytics.m | 35 +++++++++++-- Segment/Classes/SEGAnalyticsConfiguration.h | 8 ++- Segment/Classes/SEGAnalyticsConfiguration.m | 8 ++- Segment/Classes/SEGReachability.h | 4 ++ Segment/Classes/SEGReachability.m | 3 +- Segment/Classes/SEGScreenReporting.h | 2 +- Segment/Classes/SEGSegmentIntegration.m | 8 ++- Segment/Internal/SEGIntegrationsManager.m | 17 ++++++- Segment/Internal/SEGState.m | 4 ++ Segment/Internal/SEGUtils.h | 4 +- Segment/Internal/SEGUtils.m | 50 ++++++++++++++++++- Segment/Internal/UIViewController+SEGScreen.h | 2 +- Segment/Internal/UIViewController+SEGScreen.m | 2 +- 14 files changed, 134 insertions(+), 19 deletions(-) diff --git a/Segment.xcodeproj/project.pbxproj b/Segment.xcodeproj/project.pbxproj index 3da87f46f..7644d53e0 100644 --- a/Segment.xcodeproj/project.pbxproj +++ b/Segment.xcodeproj/project.pbxproj @@ -738,6 +738,9 @@ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; }; name = Debug; }; @@ -762,6 +765,9 @@ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; }; name = Release; }; diff --git a/Segment/Classes/SEGAnalytics.m b/Segment/Classes/SEGAnalytics.m index b82a2eeab..8832abf0b 100644 --- a/Segment/Classes/SEGAnalytics.m +++ b/Segment/Classes/SEGAnalytics.m @@ -60,7 +60,17 @@ - (instancetype)initWithConfiguration:(SEGAnalyticsConfiguration *)configuration // Pass through for application state change events id application = configuration.application; if (application) { -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH + // Attach to application state change hooks + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + for (NSString *name in @[ WKApplicationDidEnterBackgroundNotification, + WKApplicationDidFinishLaunchingNotification, + WKApplicationWillEnterForegroundNotification, + WKApplicationWillResignActiveNotification, + WKApplicationDidBecomeActiveNotification ]) { + [nc addObserver:self selector:@selector(handleAppStateNotification:) name:name object:application]; + } +#elif TARGET_OS_IPHONE // Attach to application state change hooks NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; for (NSString *name in @[ UIApplicationDidEnterBackgroundNotification, @@ -85,7 +95,7 @@ - (instancetype)initWithConfiguration:(SEGAnalyticsConfiguration *)configuration #endif } -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH if (configuration.recordScreenViews) { [UIViewController seg_swizzleViewDidAppear]; } @@ -98,7 +108,7 @@ - (instancetype)initWithConfiguration:(SEGAnalyticsConfiguration *)configuration _storeKitTracker = [SEGStoreKitTracker trackTransactionsForAnalytics:self]; } -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_WATCH if (configuration.trackPushNotifications && configuration.launchOptions) { #if TARGET_OS_IOS NSDictionary *remoteNotification = configuration.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; @@ -128,7 +138,22 @@ - (void)dealloc NSString *const SEGBuildKeyV1 = @"SEGBuildKey"; NSString *const SEGBuildKeyV2 = @"SEGBuildKeyV2"; -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH +- (void)handleAppStateNotification:(NSNotification *)note +{ + SEGApplicationLifecyclePayload *payload = [[SEGApplicationLifecyclePayload alloc] init]; + payload.notificationName = note.name; + [self run:SEGEventTypeApplicationLifecycle payload:payload]; + + if ([note.name isEqualToString:WKApplicationDidFinishLaunchingNotification]) { + [self _applicationDidFinishLaunchingWithOptions:note.userInfo]; + } else if ([note.name isEqualToString:WKApplicationWillEnterForegroundNotification]) { + [self _applicationWillEnterForeground]; + } else if ([note.name isEqualToString:WKApplicationDidEnterBackgroundNotification]) { + [self _applicationDidEnterBackground]; + } +} +#elif TARGET_OS_IPHONE - (void)handleAppStateNotification:(NSNotification *)note { SEGApplicationLifecyclePayload *payload = [[SEGApplicationLifecyclePayload alloc] init]; @@ -198,8 +223,10 @@ - (void)_applicationDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions @"from_background" : @NO, @"version" : currentVersion ?: @"", @"build" : currentBuild ?: @"", +#if !TARGET_OS_WATCH @"referring_application" : launchOptions[UIApplicationLaunchOptionsSourceApplicationKey] ?: @"", @"url" : launchOptions[UIApplicationLaunchOptionsURLKey] ?: @"", +#endif }]; #elif TARGET_OS_OSX [self track:@"Application Opened" properties:@{ diff --git a/Segment/Classes/SEGAnalyticsConfiguration.h b/Segment/Classes/SEGAnalyticsConfiguration.h index bec98c8e4..441976cca 100644 --- a/Segment/Classes/SEGAnalyticsConfiguration.h +++ b/Segment/Classes/SEGAnalyticsConfiguration.h @@ -8,7 +8,9 @@ @import Foundation; -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH +@import WatchKit; +#elif TARGET_OS_IPHONE @import UIKit; #elif TARGET_OS_OSX @import Cocoa; @@ -17,7 +19,9 @@ NS_SWIFT_NAME(ApplicationProtocol) @protocol SEGApplicationProtocol -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH +@property (nullable, nonatomic, assign) id delegate; +#elif TARGET_OS_IPHONE @property (nullable, nonatomic, assign) id delegate; #elif TARGET_OS_OSX @property (nullable, nonatomic, assign) id delegate; diff --git a/Segment/Classes/SEGAnalyticsConfiguration.m b/Segment/Classes/SEGAnalyticsConfiguration.m index b6342b9fd..6503d4e3c 100644 --- a/Segment/Classes/SEGAnalyticsConfiguration.m +++ b/Segment/Classes/SEGAnalyticsConfiguration.m @@ -18,7 +18,7 @@ @import Cocoa; #endif -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH @implementation UIApplication (SEGApplicationProtocol) - (UIBackgroundTaskIdentifier)seg_beginBackgroundTaskWithName:(nullable NSString *)taskName expirationHandler:(void (^__nullable)(void))handler @@ -96,7 +96,11 @@ - (instancetype)init @"(fb\\d+://authorize#access_token=)([^ ]+)": @"$1((redacted/fb-auth-token))" }; _factories = [NSMutableArray array]; -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH + if ([WKApplication respondsToSelector:@selector(sharedApplication)]){ + _application = [WKApplication performSelector:@selector(sharedApplication)]; + } +#elif TARGET_OS_IPHONE if ([UIApplication respondsToSelector:@selector(sharedApplication)]) { _application = [UIApplication performSelector:@selector(sharedApplication)]; } diff --git a/Segment/Classes/SEGReachability.h b/Segment/Classes/SEGReachability.h index e96e2cda9..8b31f264c 100644 --- a/Segment/Classes/SEGReachability.h +++ b/Segment/Classes/SEGReachability.h @@ -1,3 +1,6 @@ +#import + +#if !TARGET_OS_WATCH /* Copyright (c) 2011, Tony Million. All rights reserved. @@ -103,3 +106,4 @@ NS_SWIFT_NAME(Reachability) @end NS_ASSUME_NONNULL_END +#endif diff --git a/Segment/Classes/SEGReachability.m b/Segment/Classes/SEGReachability.m index 220cb9456..ad6623fe2 100644 --- a/Segment/Classes/SEGReachability.m +++ b/Segment/Classes/SEGReachability.m @@ -29,7 +29,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import #import "SEGReachability.h" - +#if !TARGET_OS_WATCH NSString *const kSEGReachabilityChangedNotification = @"kSEGReachabilityChangedNotification"; @@ -497,3 +497,4 @@ - (NSString *) description } @end +#endif diff --git a/Segment/Classes/SEGScreenReporting.h b/Segment/Classes/SEGScreenReporting.h index ffe7038b0..55bdd2a8c 100644 --- a/Segment/Classes/SEGScreenReporting.h +++ b/Segment/Classes/SEGScreenReporting.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol SEGScreenReporting @optional -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH - (void)seg_trackScreen:(UIViewController*)screen name:(NSString*)name; @property (readonly, nullable) UIViewController *seg_mainViewController; #elif TARGET_OS_OSX diff --git a/Segment/Classes/SEGSegmentIntegration.m b/Segment/Classes/SEGSegmentIntegration.m index 4e11bb0af..f869fdf49 100644 --- a/Segment/Classes/SEGSegmentIntegration.m +++ b/Segment/Classes/SEGSegmentIntegration.m @@ -34,7 +34,9 @@ @interface SEGSegmentIntegration () @property (nonatomic, strong) NSMutableArray *queue; @property (nonatomic, strong) NSURLSessionUploadTask *batchRequest; +#if !TARGET_OS_WATCH @property (nonatomic, strong) SEGReachability *reachability; +#endif @property (nonatomic, strong) NSTimer *flushTimer; @property (nonatomic, strong) dispatch_queue_t serialQueue; @property (nonatomic, strong) dispatch_queue_t backgroundTaskQueue; @@ -47,7 +49,7 @@ @interface SEGSegmentIntegration () @property (nonatomic, strong) id fileStorage; @property (nonatomic, strong) id userDefaultsStorage; -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH @property (nonatomic, assign) UIBackgroundTaskIdentifier flushTaskID; #else @property (nonatomic, assign) NSUInteger flushTaskID; @@ -70,11 +72,13 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics httpClient:(SEGHTTPClient *)ht self.httpClient.httpSessionDelegate = analytics.oneTimeConfiguration.httpSessionDelegate; self.fileStorage = fileStorage; self.userDefaultsStorage = userDefaultsStorage; +#if !TARGET_OS_WATCH self.reachability = [SEGReachability reachabilityWithHostname:@"google.com"]; [self.reachability startNotifier]; +#endif self.serialQueue = seg_dispatch_queue_create_specific("io.segment.analytics.segmentio", DISPATCH_QUEUE_SERIAL); self.backgroundTaskQueue = seg_dispatch_queue_create_specific("io.segment.analytics.backgroundTask", DISPATCH_QUEUE_SERIAL); -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH self.flushTaskID = UIBackgroundTaskInvalid; #else self.flushTaskID = 0; // the actual value of UIBackgroundTaskInvalid diff --git a/Segment/Internal/SEGIntegrationsManager.m b/Segment/Internal/SEGIntegrationsManager.m index ea550b630..c32c711d1 100644 --- a/Segment/Internal/SEGIntegrationsManager.m +++ b/Segment/Internal/SEGIntegrationsManager.m @@ -127,7 +127,7 @@ - (instancetype _Nonnull)initWithAnalytics:(SEGAnalytics *_Nonnull)analytics if (application) { // Attach to application state change hooks NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH [nc addObserver:self selector:@selector(onAppForeground:) name:UIApplicationWillEnterForegroundNotification object:application]; #elif TARGET_OS_OSX [nc addObserver:self selector:@selector(onAppForeground:) name:NSApplicationWillBecomeActiveNotification object:application]; @@ -167,7 +167,20 @@ - (void)handleAppStateNotification:(NSString *)notificationName static NSDictionary *selectorMapping; static dispatch_once_t selectorMappingOnce; dispatch_once(&selectorMappingOnce, ^{ -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH + selectorMapping = @{ + WKApplicationDidFinishLaunchingNotification : + NSStringFromSelector(@selector(applicationDidFinishLaunching:)), + WKApplicationDidEnterBackgroundNotification : + NSStringFromSelector(@selector(applicationDidEnterBackground)), + WKApplicationWillEnterForegroundNotification : + NSStringFromSelector(@selector(applicationWillEnterForeground)), + WKApplicationWillResignActiveNotification : + NSStringFromSelector(@selector(applicationWillResignActive)), + WKApplicationDidBecomeActiveNotification : + NSStringFromSelector(@selector(applicationDidBecomeActive)) + }; +#elif TARGET_OS_IPHONE selectorMapping = @{ UIApplicationDidFinishLaunchingNotification : diff --git a/Segment/Internal/SEGState.m b/Segment/Internal/SEGState.m index b5791b7cf..812671668 100644 --- a/Segment/Internal/SEGState.m +++ b/Segment/Internal/SEGState.m @@ -108,7 +108,9 @@ - (void)setTraits:(NSDictionary *)traits @implementation SEGPayloadContext @synthesize state; +#if !TARGET_OS_WATCH @synthesize reachability; +#endif @synthesize referrer = _referrer; @synthesize cachedStaticContext = _cachedStaticContext; @@ -118,8 +120,10 @@ - (instancetype)initWithState:(SEGState *)aState { if (self = [super init]) { self.state = aState; +#if !TARGET_OS_WATCH self.reachability = [SEGReachability reachabilityWithHostname:@"google.com"]; [self.reachability startNotifier]; +#endif } return self; } diff --git a/Segment/Internal/SEGUtils.h b/Segment/Internal/SEGUtils.h index ac8a14609..0d3a75a80 100644 --- a/Segment/Internal/SEGUtils.h +++ b/Segment/Internal/SEGUtils.h @@ -36,7 +36,9 @@ NSDictionary *getLiveContext(SEGReachability *reachability, NSDictionary * _Null NSString *GenerateUUIDString(void); -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH +NSDictionary *watchSpecifications(SEGAnalyticsConfiguration *configuration, NSString * _Nullable deviceToken); +#elif TARGET_OS_IPHONE NSDictionary *mobileSpecifications(SEGAnalyticsConfiguration *configuration, NSString * _Nullable deviceToken); #elif TARGET_OS_OSX NSDictionary *desktopSpecifications(SEGAnalyticsConfiguration *configuration, NSString * _Nullable deviceToken); diff --git a/Segment/Internal/SEGUtils.m b/Segment/Internal/SEGUtils.m index 8781a1f85..bc0bdcc72 100644 --- a/Segment/Internal/SEGUtils.m +++ b/Segment/Internal/SEGUtils.m @@ -201,7 +201,9 @@ BOOL getAdTrackingEnabled(SEGAnalyticsConfiguration *configuration) } NSDictionary *settingsDictionary = nil; -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH + settingsDictionary = watchSpecifications(configuration, deviceToken); +#elif TARGET_OS_IPHONE settingsDictionary = mobileSpecifications(configuration, deviceToken); #elif TARGET_OS_OSX settingsDictionary = desktopSpecifications(configuration, deviceToken); @@ -216,7 +218,49 @@ BOOL getAdTrackingEnabled(SEGAnalyticsConfiguration *configuration) return dict; } -#if TARGET_OS_IPHONE +#if TARGET_OS_WATCH +NSDictionary *watchSpecifications(SEGAnalyticsConfiguration *configuration, NSString *deviceToken) +{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + WKInterfaceDevice *device = [WKInterfaceDevice currentDevice]; + dict[@"device"] = ({ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + dict[@"manufacturer"] = @"Apple"; + dict[@"type"] = @"watchos"; + dict[@"name"] = [device model]; + dict[@"model"] = getDeviceModel(); + dict[@"id"] = [[device identifierForVendor] UUIDString]; + if (getAdTrackingEnabled(configuration)) { + NSString *idfa = configuration.adSupportBlock(); + // This isn't ideal. We're doing this because we can't actually check if IDFA is enabled on + // the customer device. Apple docs and tests show that if it is disabled, one gets back all 0's. + BOOL adTrackingEnabled = (![idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]); + dict[@"adTrackingEnabled"] = @(adTrackingEnabled); + + if (adTrackingEnabled) { + dict[@"advertisingId"] = idfa; + } + } + if (deviceToken && deviceToken.length > 0) { + dict[@"token"] = deviceToken; + } + dict; + }); + + dict[@"os"] = @{ + @"name" : device.systemName, + @"version" : device.systemVersion + }; + + CGSize screenSize = device.screenBounds.size; + dict[@"screen"] = @{ + @"width" : @(screenSize.width), + @"height" : @(screenSize.height) + }; + + return dict; +} +#elif TARGET_OS_IPHONE NSDictionary *mobileSpecifications(SEGAnalyticsConfiguration *configuration, NSString *deviceToken) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; @@ -360,10 +404,12 @@ BOOL getAdTrackingEnabled(SEGAnalyticsConfiguration *configuration) context[@"network"] = ({ NSMutableDictionary *network = [[NSMutableDictionary alloc] init]; +#if !TARGET_OS_WATCH if (reachability.isReachable) { network[@"wifi"] = @(reachability.isReachableViaWiFi); network[@"cellular"] = @(reachability.isReachableViaWWAN); } +#endif #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST static dispatch_once_t networkInfoOnceToken; diff --git a/Segment/Internal/UIViewController+SEGScreen.h b/Segment/Internal/UIViewController+SEGScreen.h index 6b81b6049..1baae034c 100644 --- a/Segment/Internal/UIViewController+SEGScreen.h +++ b/Segment/Internal/UIViewController+SEGScreen.h @@ -1,6 +1,6 @@ #import "SEGSerializableValue.h" -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH @import UIKit; @interface UIViewController (SEGScreen) diff --git a/Segment/Internal/UIViewController+SEGScreen.m b/Segment/Internal/UIViewController+SEGScreen.m index 8fd35623a..cd4ba36f8 100644 --- a/Segment/Internal/UIViewController+SEGScreen.m +++ b/Segment/Internal/UIViewController+SEGScreen.m @@ -5,7 +5,7 @@ #import "SEGScreenReporting.h" -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH @implementation UIViewController (SEGScreen) + (void)seg_swizzleViewDidAppear