From 6d680a7b37dc8a7a5a7dcb5d17261437c2e12e86 Mon Sep 17 00:00:00 2001 From: Dean Shi Date: Thu, 2 Jun 2022 11:10:21 -0700 Subject: [PATCH] feat: update iOS plugin version (#102) --- Assets/Amplitude/Amplitude.cs | 2 +- Assets/Plugins/iOS/Amplitude/AMPConstants.h | 1 + Assets/Plugins/iOS/Amplitude/AMPConstants.m | 3 +- Assets/Plugins/iOS/Amplitude/AMPDeviceInfo.m | 13 +- Assets/Plugins/iOS/Amplitude/AMPMiddleware.h | 60 +++++++ .../iOS/Amplitude/AMPMiddleware.h.meta | 33 ++++ Assets/Plugins/iOS/Amplitude/AMPMiddleware.m | 52 ++++++ .../iOS/Amplitude/AMPMiddleware.m.meta | 33 ++++ .../iOS/Amplitude/AMPMiddlewareRunner.h | 36 +++++ .../iOS/Amplitude/AMPMiddlewareRunner.h.meta | 33 ++++ .../iOS/Amplitude/AMPMiddlewareRunner.m | 65 ++++++++ .../iOS/Amplitude/AMPMiddlewareRunner.m.meta | 33 ++++ Assets/Plugins/iOS/Amplitude/AMPPlan.h | 4 + Assets/Plugins/iOS/Amplitude/AMPPlan.m | 13 ++ Assets/Plugins/iOS/Amplitude/Amplitude.h | 19 ++- Assets/Plugins/iOS/Amplitude/Amplitude.m | 58 ++++++- .../iOS/Amplitude/AnalyticsConnector.h | 27 ++++ .../iOS/Amplitude/AnalyticsConnector.h.meta | 33 ++++ .../iOS/Amplitude/AnalyticsConnector.swift | 36 +++++ .../Amplitude/AnalyticsConnector.swift.meta | 33 ++++ Assets/Plugins/iOS/Amplitude/EventBridge.h | 32 ++++ .../Plugins/iOS/Amplitude/EventBridge.h.meta | 33 ++++ .../Plugins/iOS/Amplitude/EventBridge.swift | 77 +++++++++ .../iOS/Amplitude/EventBridge.swift.meta | 33 ++++ Assets/Plugins/iOS/Amplitude/IdentityStore.h | 43 +++++ .../iOS/Amplitude/IdentityStore.h.meta | 33 ++++ .../Plugins/iOS/Amplitude/IdentityStore.swift | 151 ++++++++++++++++++ .../iOS/Amplitude/IdentityStore.swift.meta | 33 ++++ 28 files changed, 1015 insertions(+), 7 deletions(-) create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddleware.h create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddleware.h.meta create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddleware.m create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddleware.m.meta create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h.meta create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m create mode 100644 Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m.meta create mode 100644 Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h create mode 100644 Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h.meta create mode 100644 Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift create mode 100644 Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift.meta create mode 100644 Assets/Plugins/iOS/Amplitude/EventBridge.h create mode 100644 Assets/Plugins/iOS/Amplitude/EventBridge.h.meta create mode 100644 Assets/Plugins/iOS/Amplitude/EventBridge.swift create mode 100644 Assets/Plugins/iOS/Amplitude/EventBridge.swift.meta create mode 100644 Assets/Plugins/iOS/Amplitude/IdentityStore.h create mode 100644 Assets/Plugins/iOS/Amplitude/IdentityStore.h.meta create mode 100644 Assets/Plugins/iOS/Amplitude/IdentityStore.swift create mode 100644 Assets/Plugins/iOS/Amplitude/IdentityStore.swift.meta diff --git a/Assets/Amplitude/Amplitude.cs b/Assets/Amplitude/Amplitude.cs index 166d23b..7e2727f 100644 --- a/Assets/Amplitude/Amplitude.cs +++ b/Assets/Amplitude/Amplitude.cs @@ -9,7 +9,7 @@ public class Amplitude { private static readonly string UnityLibraryName = "amplitude-unity"; - private static readonly string UnityLibraryVersion = "2.4.0"; + private static readonly string UnityLibraryVersion = "2.5.0"; private static Dictionary instances; private static readonly object instanceLock = new object(); diff --git a/Assets/Plugins/iOS/Amplitude/AMPConstants.h b/Assets/Plugins/iOS/Amplitude/AMPConstants.h index e1cc054..ae4fb4e 100644 --- a/Assets/Plugins/iOS/Amplitude/AMPConstants.h +++ b/Assets/Plugins/iOS/Amplitude/AMPConstants.h @@ -90,3 +90,4 @@ extern NSString *const AMP_TRACKING_OPTION_VERSION_NAME; extern NSString *const AMP_PLAN_BRANCH; extern NSString *const AMP_PLAN_SOURCE; extern NSString *const AMP_PLAN_VERSION; +extern NSString *const AMP_PLAN_VERSION_ID; diff --git a/Assets/Plugins/iOS/Amplitude/AMPConstants.m b/Assets/Plugins/iOS/Amplitude/AMPConstants.m index 3c13572..125f657 100644 --- a/Assets/Plugins/iOS/Amplitude/AMPConstants.m +++ b/Assets/Plugins/iOS/Amplitude/AMPConstants.m @@ -24,7 +24,7 @@ #import "AMPConstants.h" NSString *const kAMPLibrary = @"amplitude-ios"; -NSString *const kAMPVersion = @"8.5.0"; // Version is managed automatically by semantic-release, please don't change it manually +NSString *const kAMPVersion = @"8.10.0"; // Version is managed automatically by semantic-release, please don't change it manually NSString *const kAMPUnknownLibrary = @"unknown-library"; NSString *const kAMPUnknownVersion = @"unknown-version"; NSString *const kAMPEventLogDomain = @"api2.amplitude.com"; @@ -111,3 +111,4 @@ NSString *const AMP_PLAN_BRANCH = @"branch"; NSString *const AMP_PLAN_SOURCE = @"source"; NSString *const AMP_PLAN_VERSION = @"version"; +NSString *const AMP_PLAN_VERSION_ID = @"versionId"; diff --git a/Assets/Plugins/iOS/Amplitude/AMPDeviceInfo.m b/Assets/Plugins/iOS/Amplitude/AMPDeviceInfo.m index 2f82f8a..b90499c 100644 --- a/Assets/Plugins/iOS/Amplitude/AMPDeviceInfo.m +++ b/Assets/Plugins/iOS/Amplitude/AMPDeviceInfo.m @@ -188,10 +188,17 @@ + (NSString *)generateUUID { } + (NSString *)getPlatformString { -#if !TARGET_OS_OSX - const char *sysctl_name = "hw.machine"; -#else const char *sysctl_name = "hw.model"; +#if TARGET_OS_IOS + BOOL isiOSAppOnMac = NO; + if (@available(iOS 14.0, *)) { + isiOSAppOnMac = [NSProcessInfo processInfo].isiOSAppOnMac; + } + if (!isiOSAppOnMac){ + sysctl_name = "hw.machine"; + } +#elif TARGET_OS_TV || TARGET_OS_WATCH + sysctl_name = "hw.machine"; #endif size_t size; sysctlbyname(sysctl_name, NULL, &size, NULL, 0); diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h new file mode 100644 index 0000000..63e2fad --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h @@ -0,0 +1,60 @@ +// +// AMPMiddleware.h +// Copyright (c) 2021 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import + +/** + * AMPMiddlewarePayload + */ +@interface AMPMiddlewarePayload: NSObject + +@property NSMutableDictionary *_Nonnull event; +@property NSMutableDictionary *_Nullable extra; + +- (instancetype _Nonnull)initWithEvent:(NSMutableDictionary *_Nonnull) event withExtra:(NSMutableDictionary *_Nullable) extra; + +@end + +/** + * AMPMiddleware + */ +typedef void (^AMPMiddlewareNext)(AMPMiddlewarePayload *_Nullable newPayload); + +@protocol AMPMiddleware + +- (void)run:(AMPMiddlewarePayload *_Nonnull)payload next:(AMPMiddlewareNext _Nonnull)next; + +@end + +/** + * AMPBlockMiddleware + */ +typedef void (^AMPMiddlewareBlock)(AMPMiddlewarePayload *_Nonnull payload, AMPMiddlewareNext _Nonnull next); + +@interface AMPBlockMiddleware : NSObject + +@property (nonnull, nonatomic, readonly) AMPMiddlewareBlock block; + +- (instancetype _Nonnull)initWithBlock:(AMPMiddlewareBlock _Nonnull)block; + +@end diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h.meta b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h.meta new file mode 100644 index 0000000..5ee2a2f --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.h.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 029d82d7c1b8f4081aac50411debf28b +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m new file mode 100644 index 0000000..09b67b1 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m @@ -0,0 +1,52 @@ +// +// AMPMiddleware.m +// Copyright (c) 2021 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import +#import "AMPMiddleware.h" + +@implementation AMPMiddlewarePayload + +- (instancetype _Nonnull)initWithEvent:(NSMutableDictionary *_Nonnull) event withExtra:(NSMutableDictionary *_Nullable) extra { + if ((self = [super init])) { + self.event = event; + self.extra = extra; + } + return self; +} + +@end + +@implementation AMPBlockMiddleware + +- (instancetype _Nonnull)initWithBlock:(AMPMiddlewareBlock)block { + if (self = [super init]) { + _block = block; + } + return self; +} + +- (void)run:(AMPMiddlewarePayload *)payload next:(AMPMiddlewareNext)next { + self.block(payload, next); +} + +@end diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m.meta b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m.meta new file mode 100644 index 0000000..669ff75 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddleware.m.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 154897f05b89348be80f4bc9c4d2186d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h new file mode 100644 index 0000000..372a4dc --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h @@ -0,0 +1,36 @@ +// +// AMPMiddlewareRunner.h +// Copyright (c) 2021 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#import +#import "AMPMiddleware.h" + +@interface AMPMiddlewareRunner : NSObject + +@property (nonatomic, nonnull, readonly) NSMutableArray> *middlewares; + ++ (instancetype _Nonnull)middleRunner; + +- (void) add:(id _Nonnull)middleware; + +- (void) run:(AMPMiddlewarePayload *_Nonnull)payload next:(AMPMiddlewareNext _Nonnull)next; + +@end diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h.meta b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h.meta new file mode 100644 index 0000000..9af6b4c --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.h.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: b0deb52868ced4f97afe685348bbe7c5 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m new file mode 100644 index 0000000..b82e45f --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m @@ -0,0 +1,65 @@ +// +// AMPMiddlewareRunner.m +// Copyright (c) 2021 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import +#import "AMPMiddlewareRunner.h" +#import "AMPMiddleware.h" + +@implementation AMPMiddlewareRunner + +- (instancetype)init { + if ((self = [super init])) { + _middlewares = [[NSMutableArray alloc] init]; + } + return self; +} + ++ (instancetype _Nonnull)middleRunner { + return [[self alloc] init]; +} + +- (void) add:(id _Nonnull)middleware { + [self.middlewares addObject:middleware]; +} + +- (void) run:(AMPMiddlewarePayload *_Nonnull)payload next:(AMPMiddlewareNext _Nonnull)next { + [self runMiddlewares:self.middlewares payload:payload callback:next]; +} + +- (void) runMiddlewares:(NSArray> *_Nonnull)middlewares + payload:(AMPMiddlewarePayload *_Nonnull)payload + callback:(AMPMiddlewareNext _Nullable)callback { + if (middlewares.count == 0) { + if (callback) { + callback(payload); + } + return; + } + + [middlewares[0] run:payload next:^(AMPMiddlewarePayload *_Nullable newPayload) { + NSArray *remainingMiddlewares = [middlewares subarrayWithRange:NSMakeRange(1, middlewares.count - 1)]; + [self runMiddlewares:remainingMiddlewares payload:newPayload callback:callback]; + }]; +} + +@end diff --git a/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m.meta b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m.meta new file mode 100644 index 0000000..124e34c --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AMPMiddlewareRunner.m.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: e9e254277ade5482f8a9c5478713ffe5 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/AMPPlan.h b/Assets/Plugins/iOS/Amplitude/AMPPlan.h index 83c62ef..3011932 100644 --- a/Assets/Plugins/iOS/Amplitude/AMPPlan.h +++ b/Assets/Plugins/iOS/Amplitude/AMPPlan.h @@ -30,6 +30,8 @@ @property (nonatomic, strong, readonly) NSString *version; +@property (nonatomic, strong, readonly) NSString *versionId; + + (instancetype)plan; - (AMPPlan *)setBranch:(NSString *)branch; @@ -38,6 +40,8 @@ - (AMPPlan *)setVersion:(NSString *)version; +- (AMPPlan *)setVersionId:(NSString *)versionId; + - (NSDictionary *)toNSDictionary; @end diff --git a/Assets/Plugins/iOS/Amplitude/AMPPlan.m b/Assets/Plugins/iOS/Amplitude/AMPPlan.m index 4769538..1119200 100644 --- a/Assets/Plugins/iOS/Amplitude/AMPPlan.m +++ b/Assets/Plugins/iOS/Amplitude/AMPPlan.m @@ -72,6 +72,16 @@ - (AMPPlan *)setVersion:(NSString *)version { return self; } +- (AMPPlan *)setVersionId:(NSString *)versionId { + if ([AMPUtils isEmptyString:versionId]) { + AMPLITUDE_LOG(@"Invalid empty versionId"); + return self; + } + + _versionId = versionId; + return self; +} + - (NSDictionary *)toNSDictionary { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; if (_branch) { @@ -83,6 +93,9 @@ - (NSDictionary *)toNSDictionary { if (_version) { [dict setValue:_version forKey:AMP_PLAN_VERSION]; } + if (_versionId) { + [dict setValue:_versionId forKey:AMP_PLAN_VERSION_ID]; + } return dict; } diff --git a/Assets/Plugins/iOS/Amplitude/Amplitude.h b/Assets/Plugins/iOS/Amplitude/Amplitude.h index fc973f5..77b2ce5 100644 --- a/Assets/Plugins/iOS/Amplitude/Amplitude.h +++ b/Assets/Plugins/iOS/Amplitude/Amplitude.h @@ -27,6 +27,8 @@ #import "AMPTrackingOptions.h" #import "AMPPlan.h" #import "AMPServerZone.h" +#import "AMPMiddleware.h" +#import "AnalyticsConnector.h" NS_ASSUME_NONNULL_BEGIN @@ -278,6 +280,9 @@ typedef void (^AMPInitCompletionBlock)(void); */ - (void)logEvent:(NSString *)eventType withEventProperties:(nullable NSDictionary *)eventProperties; + +- (void)logEvent:(NSString *)eventType withEventProperties:(nullable NSDictionary *)eventProperties withMiddlewareExtra: (nullable NSMutableDictionary *) extra; + /** Tracks an event. Events are saved locally. @@ -586,6 +591,13 @@ typedef void (^AMPInitCompletionBlock)(void); */ - (void)setOptOut:(BOOL)enabled; +/** + Sets event upload max batch size. This controls the maximum number of events sent with each upload request. + + @param eventUploadMaxBatchSize Set the event upload max batch size + */ +- (void)updateEventUploadMaxBatchSize:(int)eventUploadMaxBatchSize; + /** Disables sending logged events to Amplitude servers. @@ -627,7 +639,7 @@ typedef void (^AMPInitCompletionBlock)(void); /** Sends events to a different URL other than kAMPEventLogUrl. Used for proxy servers - + We now have a new method setServerZone. To send data to Amplitude's EU servers, recommend to use setServerZone method like [client setServerZone:EU] */ @@ -654,6 +666,11 @@ typedef void (^AMPInitCompletionBlock)(void); */ - (void)setServerZone:(AMPServerZone)serverZone updateServerUrl:(BOOL)updateServerUrl; +/** + * Adds a new middleware function to run on each logEvent() call prior to sending to Amplitude. + */ +- (void)addEventMiddleware:(id _Nonnull)middleware; + /**----------------------------------------------------------------------------- * @name Other Methods * ----------------------------------------------------------------------------- diff --git a/Assets/Plugins/iOS/Amplitude/Amplitude.m b/Assets/Plugins/iOS/Amplitude/Amplitude.m index 2e725d0..58fc060 100644 --- a/Assets/Plugins/iOS/Amplitude/Amplitude.m +++ b/Assets/Plugins/iOS/Amplitude/Amplitude.m @@ -62,6 +62,11 @@ #import "AMPPlan.h" #import "AMPServerZone.h" #import "AMPServerZoneUtil.h" +#import "AMPMiddleware.h" +#import "AMPMiddlewareRunner.h" +#import "AnalyticsConnector.h" +#import "EventBridge.h" +#import "IdentityStore.h" #import #import @@ -138,6 +143,7 @@ @implementation Amplitude { NSString *_token; AMPPlan *_plan; AMPServerZone _serverZone; + AMPMiddlewareRunner *_middlewareRunner; } #pragma clang diagnostic push @@ -210,6 +216,7 @@ - (instancetype)initWithInstanceName:(NSString *)instanceName { _coppaControlEnabled = NO; self.instanceName = instanceName; _dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName]; + _middlewareRunner = [AMPMiddlewareRunner middleRunner]; self.eventUploadThreshold = kAMPEventUploadThreshold; self.eventMaxCount = kAMPEventMaxCount; @@ -217,6 +224,11 @@ - (instancetype)initWithInstanceName:(NSString *)instanceName { self.eventUploadPeriodSeconds = kAMPEventUploadPeriodSeconds; self.minTimeBetweenSessionsMillis = kAMPMinTimeBetweenSessionsMillis; _backoffUploadBatchSize = self.eventUploadMaxBatchSize; + + // Set the event receiver to forward events generated by Experiment SDK + [[[AnalyticsConnector getInstance:self.instanceName] eventBridge] setEventReceiver:^(AnalyticsEvent * _Nonnull event) { + [self logEvent:[event eventType] withEventProperties:[event eventProperties] withApiProperties:nil withUserProperties:[event userProperties] withGroups:nil withGroupProperties:nil withTimestamp:nil outOfSession:false]; + }]; _initializerQueue = [[NSOperationQueue alloc] init]; _backgroundQueue = [[NSOperationQueue alloc] init]; @@ -444,6 +456,10 @@ - (void)initializeApiKey:(NSString *)apiKey } else { self.userId = [self.dbHelper getValue:USER_ID]; } + // Set the user ID and device ID in the amplitude core instance. This is used to share user identity and user properties + // between Analytics and Experiment SDKs. + id identityStoreEditor = [[[AnalyticsConnector getInstance:self.instanceName] identityStore] editIdentity]; + [[[identityStoreEditor setUserId:self.userId] setDeviceId:self.deviceId] commit]; if (self.initCompletionBlock != nil) { self.initCompletionBlock(); } @@ -513,6 +529,10 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event [self logEvent:eventType withEventProperties:eventProperties withGroups:nil]; } +- (void)logEvent:(NSString *)eventType withEventProperties:(nullable NSDictionary *)eventProperties withMiddlewareExtra: (nullable NSMutableDictionary *) extra { + [self logEvent:eventType withEventProperties:eventProperties withApiProperties:nil withUserProperties:nil withGroups:nil withGroupProperties:nil withTimestamp:nil outOfSession:NO withMiddlewareExtra:extra]; +} + - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)eventProperties outOfSession:(BOOL)outOfSession { [self logEvent:eventType withEventProperties:eventProperties withGroups:nil outOfSession:outOfSession]; } @@ -534,6 +554,10 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event } - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)eventProperties withApiProperties:(NSDictionary *)apiProperties withUserProperties:(NSDictionary *)userProperties withGroups:(NSDictionary *)groups withGroupProperties:(NSDictionary *)groupProperties withTimestamp:(NSNumber *)timestamp outOfSession:(BOOL)outOfSession { + [self logEvent:eventType withEventProperties:eventProperties withApiProperties:apiProperties withUserProperties:userProperties withGroups:groups withGroupProperties:groupProperties withTimestamp:timestamp outOfSession:outOfSession withMiddlewareExtra:nil]; +} + +- (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)eventProperties withApiProperties:(NSDictionary *)apiProperties withUserProperties:(NSDictionary *)userProperties withGroups:(NSDictionary *)groups withGroupProperties:(NSDictionary *)groupProperties withTimestamp:(NSNumber *)timestamp outOfSession:(BOOL)outOfSession withMiddlewareExtra: (nullable NSMutableDictionary *) extra { if (self.apiKey == nil) { AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logEvent"); return; @@ -583,6 +607,19 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event [event setValue:timestamp forKey:@"timestamp"]; [self annotateEvent:event]; + + AMPMiddlewarePayload * middlewarePayload = [[AMPMiddlewarePayload alloc] initWithEvent:event withExtra:extra]; + + __block BOOL middlewareCompleted = NO; + + [self->_middlewareRunner run:middlewarePayload next:^(AMPMiddlewarePayload *_Nullable newPayload){ + middlewareCompleted = YES; + }]; + + if (!middlewareCompleted) { + AMPLITUDE_LOG(@"Middleware chain skipped logEvent action. Event %@ not logged.", eventType); + return; + } // convert event dictionary to JSON String NSError *error = nil; @@ -601,6 +638,12 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event } else { (void) [self.dbHelper addEvent:jsonString]; } + + // Apply identify events to amplitude core to notify experiment SDK that user properties have changed. + if ([eventType isEqualToString:IDENTIFY_EVENT]) { + id editor = [[[AnalyticsConnector getInstance:self.instanceName] identityStore] editIdentity]; + [[editor updateUserProperties:[event valueForKey:@"user_properties"]] commit]; + } AMPLITUDE_LOG(@"Logged %@ Event", event[@"event_type"]); @@ -1351,6 +1394,11 @@ - (void)setUserId:(NSString *)userId startNewSession:(BOOL)startNewSession { self->_userId = userId; [self.dbHelper insertOrReplaceKeyValue:USER_ID value:self.userId]; + + // Set the user ID amplitude core instance. This is used to share user identity + // between Analytics and Experiment SDKs. + id identityStoreEditor = [[[AnalyticsConnector getInstance:self.instanceName] identityStore] editIdentity]; + [[identityStoreEditor setUserId:self.userId] commit]; if (startNewSession) { NSNumber *timestamp = [NSNumber numberWithLongLong:[[self currentTime] timeIntervalSince1970] * 1000]; @@ -1402,7 +1450,7 @@ - (void)setBearerToken:(NSString *)token { self->_token = token; } -- (void)setEventUploadMaxBatchSize:(int)eventUploadMaxBatchSize { +- (void)updateEventUploadMaxBatchSize:(int)eventUploadMaxBatchSize { _eventUploadMaxBatchSize = eventUploadMaxBatchSize; _backoffUploadBatchSize = eventUploadMaxBatchSize; } @@ -1419,6 +1467,10 @@ - (void)setDeviceId:(NSString *)deviceId { [self runOnBackgroundQueue:^{ self->_deviceId = deviceId; [self.dbHelper insertOrReplaceKeyValue:DEVICE_ID value:deviceId]; + // Set the device ID in the amplitude core instance. This is used to share user identity + // between Analytics and Experiment SDKs. + id identityStoreEditor = [[[AnalyticsConnector getInstance:self.instanceName] identityStore] editIdentity]; + [[identityStoreEditor setDeviceId:self.deviceId] commit]; }]; } @@ -1447,6 +1499,10 @@ - (void)setServerZone:(AMPServerZone)serverZone updateServerUrl:(BOOL)updateServ } } +- (void)addEventMiddleware:(id _Nonnull)middleware { + [_middlewareRunner add:middleware]; +} + #pragma mark - Getters for device data - (NSString *)getAdSupportID { NSString *result = nil; diff --git a/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h new file mode 100644 index 0000000..c6df744 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h @@ -0,0 +1,27 @@ +// +// AnalyticsConnector.h +// AnalyticsConnector +// +// Created by Brian Giori on 12/20/21. +// + + +#import +#import "EventBridge.h" +#import "IdentityStore.h" + +//! Project version number for AnalyticsConnector. +FOUNDATION_EXPORT double AnalyticsConnectorVersionNumber; + +//! Project version string for AnalyticsConnector. +FOUNDATION_EXPORT const unsigned char AnalyticsConnectorVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +@class AnalyticsConnector; + +@interface AnalyticsConnector : NSObject +@property (nonatomic, strong, readonly) EventBridge *eventBridge; +@property (nonatomic, strong, readonly) IdentityStore *identityStore; + ++ (AnalyticsConnector *)getInstance:(NSString *)instanceName; +@end diff --git a/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h.meta b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h.meta new file mode 100644 index 0000000..f185729 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.h.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 10cac7f217a9c415db0e735bb4cd0f4f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift new file mode 100644 index 0000000..33bbf3c --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift @@ -0,0 +1,36 @@ +// +// AnalyticsConnector.swift +// AnalyticsConnector +// +// Created by Brian Giori on 12/20/21. +// + +import Foundation + +@objc public class AnalyticsConnector : NSObject { + + private static let instancesLock: DispatchSemaphore = DispatchSemaphore(value: 1) + private static var instances: [String:AnalyticsConnector] = [:] + + @objc public static func getInstance(_ instanceName: String) -> AnalyticsConnector { + instancesLock.wait() + defer { instancesLock.signal() } + if let instance = instances[instanceName] { + return instance + } else { + instances[instanceName] = AnalyticsConnector( + eventBridge: EventBridgeImpl(), + identityStore: IdentityStoreImpl() + ) + return instances[instanceName]! + } + } + + @objc public let eventBridge: EventBridge + @objc public let identityStore: IdentityStore + + private init(eventBridge: EventBridge, identityStore: IdentityStore) { + self.eventBridge = eventBridge + self.identityStore = identityStore + } +} diff --git a/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift.meta b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift.meta new file mode 100644 index 0000000..0604fbc --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/AnalyticsConnector.swift.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: e82c383a8bca94af19598f71776c0c20 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/EventBridge.h b/Assets/Plugins/iOS/Amplitude/EventBridge.h new file mode 100644 index 0000000..1603020 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/EventBridge.h @@ -0,0 +1,32 @@ +// +// EventBridge.h +// EventBridge +// +// Created by Dean Shi on 05/27/22. +// + + +#import + +@class AnalyticsEvent; +@class EventBridge; + + +@protocol EventBridge +- (void)setEventReceiver:(void (^_Nonnull)(AnalyticsEvent * _Nonnull))eventReceiver; +- (void)logEvent:(AnalyticsEvent *_Nonnull)event; + +@end + + +@interface AnalyticsEvent : NSObject +@property (nonatomic, strong, readonly) NSString * _Nonnull eventType; +@property (nonatomic, strong, readonly) NSDictionary * _Nonnull eventProperties; +@property (nonatomic, strong, readonly) NSDictionary * _Nonnull userProperties; + +@end + + +@interface EventBridge : NSObject + +@end diff --git a/Assets/Plugins/iOS/Amplitude/EventBridge.h.meta b/Assets/Plugins/iOS/Amplitude/EventBridge.h.meta new file mode 100644 index 0000000..0f10e7d --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/EventBridge.h.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 08615cce83f2a40ee9f66eb00c62f257 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/EventBridge.swift b/Assets/Plugins/iOS/Amplitude/EventBridge.swift new file mode 100644 index 0000000..966fa7e --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/EventBridge.swift @@ -0,0 +1,77 @@ +// +// EventBridge.swift +// EventBridge +// +// Created by Brian Giori on 12/21/21. +// + +import Foundation + +@objc public class AnalyticsEvent: NSObject { + @objc public let eventType: String + @objc public let eventProperties: NSDictionary? + @objc public let userProperties: NSDictionary? + + @objc public init(eventType: String, eventProperties: NSDictionary?, userProperties: NSDictionary?) { + self.eventType = eventType + self.eventProperties = eventProperties + self.userProperties = userProperties + } + + @objc public override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? AnalyticsEvent else { + return false + } + return self.eventType == other.eventType && + dictionaryEquals(self.eventProperties, other.eventProperties) && + dictionaryEquals(self.userProperties, other.userProperties) + } +} + +@objc public protocol EventBridge { + @objc func setEventReceiver(_ eventReceiver: @escaping (AnalyticsEvent) -> ()) + @objc func logEvent(event: AnalyticsEvent) +} + +@objc internal class EventBridgeImpl: NSObject, EventBridge { + + private let eventReceiverLock = DispatchSemaphore(value: 1) + private var eventReceiver: ((AnalyticsEvent) -> ())? = nil + private var eventQueue: [AnalyticsEvent] = [] + + @objc func setEventReceiver(_ eventReceiver: @escaping (AnalyticsEvent) -> ()) { + eventReceiverLock.wait() + self.eventReceiver = eventReceiver + let events = eventQueue + eventQueue = [] + eventReceiverLock.signal() + for event in events { + eventReceiver(event) + } + } + + @objc func logEvent(event: AnalyticsEvent) { + eventReceiverLock.wait() + defer { eventReceiverLock.signal() } + guard let eventReceiver = self.eventReceiver else { + if (eventQueue.count < 512) { + eventQueue.append(event) + } + return + } + eventReceiver(event) + } +} + +internal func dictionaryEquals(_ d1: NSDictionary?, _ d2: NSDictionary?) -> Bool { + if let d1 = d1, let d2 = d2 as? [AnyHashable:Any] { + guard d1.isEqual(to: d2) else { + return false + } + } else { + guard d1 == nil, d2 == nil else { + return false + } + } + return true +} diff --git a/Assets/Plugins/iOS/Amplitude/EventBridge.swift.meta b/Assets/Plugins/iOS/Amplitude/EventBridge.swift.meta new file mode 100644 index 0000000..e1b9c16 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/EventBridge.swift.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: cdd06fc49174e4122b633669228d201c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/IdentityStore.h b/Assets/Plugins/iOS/Amplitude/IdentityStore.h new file mode 100644 index 0000000..facb8c3 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/IdentityStore.h @@ -0,0 +1,43 @@ +// +// IdentifyStore.h +// IdentifyStore +// +// Created by Dean Shi on 05/27/22. +// + + +#import + +@class Identity; +@class IdentityStore; +@class IdentityStoreEditor; + + +@protocol IdentityStore +- (IdentityStoreEditor * _Nonnull)getIdentity; +- (IdentityStoreEditor * _Nonnull)setIdentity:(Identity *_Nonnull) identity; +- (IdentityStoreEditor * _Nonnull)editIdentity; +- (void)addIdentityListener:(NSString *_Nonnull)key listener:(void (^_Nonnull)(Identity *_Nonnull))listener; +- (void)removeIdentityListener:(NSString *_Nonnull)key; + +@end + + +@protocol IdentityStoreEditor +- (IdentityStoreEditor * _Nonnull)setUserId:(nullable NSString *)userId; +- (IdentityStoreEditor * _Nonnull)setDeviceId:(nullable NSString *)deviceId; +- (IdentityStoreEditor * _Nonnull)setUserProperties:(NSDictionary *_Nonnull)userProperties; +- (IdentityStoreEditor * _Nonnull)updateUserProperties:(NSDictionary *_Nonnull)userProperties; +- (void)commit; + +@end + + +@interface IdentityStore : NSObject + +@end + + +@interface IdentityStoreEditor : NSObject + +@end diff --git a/Assets/Plugins/iOS/Amplitude/IdentityStore.h.meta b/Assets/Plugins/iOS/Amplitude/IdentityStore.h.meta new file mode 100644 index 0000000..e1faab9 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/IdentityStore.h.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 495b247a59c6c4373af4120e70a16117 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/iOS/Amplitude/IdentityStore.swift b/Assets/Plugins/iOS/Amplitude/IdentityStore.swift new file mode 100644 index 0000000..953d9c0 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/IdentityStore.swift @@ -0,0 +1,151 @@ +// +// IdentityStore.swift +// AnalyticsConnector +// +// Created by Brian Giori on 12/21/21. +// + +import Foundation + +internal let ID_OP_SET = "$set" +internal let ID_OP_UNSET = "$unset" +internal let ID_OP_CLEAR_ALL = "$clearAll" + +@objc public class Identity: NSObject { + @objc public let userId: String? + @objc public let deviceId: String? + @objc public let userProperties: NSDictionary + @objc public init(userId: String? = nil, deviceId: String? = nil, userProperties: NSDictionary? = nil) { + self.userId = userId + self.deviceId = deviceId + self.userProperties = userProperties ?? NSDictionary() + } + @objc public override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? Identity else { + return false + } + return self.userId == other.userId && + self.deviceId == other.deviceId && + dictionaryEquals(self.userProperties, other.userProperties) + } +} + +@objc public protocol IdentityStore { + @objc func getIdentity() -> Identity + @objc func setIdentity(_ identity: Identity) + @objc func editIdentity() -> IdentityStoreEditor + @objc func addIdentityListener(key: String, _ listener: @escaping (Identity) -> ()) + @objc func removeIdentityListener(key: String) +} + +@objc public protocol IdentityStoreEditor { + @objc func setUserId(_ userId: String?) -> IdentityStoreEditor + @objc func setDeviceId(_ deviceId: String?) -> IdentityStoreEditor + @objc func setUserProperties(_ userProperties: NSDictionary) -> IdentityStoreEditor + @objc func updateUserProperties(_ userPropertyActions: NSDictionary) -> IdentityStoreEditor + @objc func commit() +} + +@objc internal class IdentityStoreImpl: NSObject, IdentityStore { + private let identityLock = DispatchSemaphore(value: 1) + private var identity = Identity() + private let listenersLock = DispatchSemaphore(value: 1) + private var listeners: [String: (Identity) -> ()] = [:] + + @objc func getIdentity() -> Identity { + identityLock.wait() + defer { identityLock.signal() } + return identity + } + + @objc func setIdentity(_ identity: Identity) { + identityLock.wait() + let identityChanged = self.identity != identity + self.identity = identity + identityLock.signal() + if identityChanged { + listenersLock.wait() + let safeListeners = listeners.values + listenersLock.signal() + for listener in safeListeners { + listener(identity) + } + } + } + + @objc func editIdentity() -> IdentityStoreEditor { + return IdentityStoreEditorImpl(identityStore: self) + } + + @objc func addIdentityListener(key: String, _ listener: @escaping (Identity) -> ()) { + listenersLock.wait() + defer { listenersLock.signal() } + listeners[key] = listener + } + + @objc func removeIdentityListener(key: String) { + listenersLock.wait() + defer { listenersLock.signal() } + listeners.removeValue(forKey: key) + } +} + +@objc internal class IdentityStoreEditorImpl: NSObject, IdentityStoreEditor { + + private let identityStore: IdentityStore + + private var userId: String? + private var deviceId: String? + private var userProperties: NSMutableDictionary + + internal init(identityStore: IdentityStore) { + let identity = identityStore.getIdentity() + self.userId = identity.userId + self.deviceId = identity.deviceId + self.userProperties = identity.userProperties.mutableCopy() as? NSMutableDictionary ?? NSMutableDictionary() + self.identityStore = identityStore + } + + @objc func setUserId(_ userId: String?) -> IdentityStoreEditor { + self.userId = userId + return self + } + + @objc func setDeviceId(_ deviceId: String?) -> IdentityStoreEditor { + self.deviceId = deviceId + return self + } + + @objc func setUserProperties(_ userProperties: NSDictionary) -> IdentityStoreEditor { + if let userProperties = userProperties.mutableCopy() as? NSMutableDictionary { + self.userProperties = userProperties + } + return self + } + + @objc func updateUserProperties(_ userPropertyActions: NSDictionary) -> IdentityStoreEditor { + userPropertyActions.forEach { (action: Any, properties: Any) in + guard let action = action as? String else { + return + } + guard let properties = properties as? [AnyHashable: Any] else { + return + } + switch (action) { + case ID_OP_SET: + self.userProperties.addEntries(from: properties) + case ID_OP_UNSET: + self.userProperties.removeObjects(forKeys: Array(properties.keys)) + case ID_OP_CLEAR_ALL: + self.userProperties.removeAllObjects() + default: break + } + } + return self + } + + @objc func commit() { + let identity = Identity(userId: userId, deviceId: deviceId, userProperties: userProperties) + self.identityStore.setIdentity(identity) + } +} diff --git a/Assets/Plugins/iOS/Amplitude/IdentityStore.swift.meta b/Assets/Plugins/iOS/Amplitude/IdentityStore.swift.meta new file mode 100644 index 0000000..87fe296 --- /dev/null +++ b/Assets/Plugins/iOS/Amplitude/IdentityStore.swift.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 28f7e466407594d7399b183212cfca3d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: