Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Integration Middleware capabilities #879

Merged
merged 7 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions Analytics.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -536,26 +536,30 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0810;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = Segment;
TargetAttributes = {
9D8CE58B23EE014E00197D0C = {
LastSwiftMigration = 1130;
};
EADEB85A1DECD080005322DA = {
CreatedOnToolsVersion = 8.1;
ProvisioningStyle = Automatic;
};
EADEB8691DECD0EF005322DA = {
CreatedOnToolsVersion = 8.1;
LastSwiftMigration = 1130;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = EADEB8551DECD080005322DA /* Build configuration list for PBXProject "Analytics" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
mainGroup = EADEB8511DECD080005322DA;
productRefGroup = EADEB85C1DECD080005322DA /* Products */;
Expand Down Expand Up @@ -822,6 +826,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand All @@ -831,13 +836,15 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand Down Expand Up @@ -881,6 +888,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand All @@ -890,13 +898,15 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
138 changes: 91 additions & 47 deletions Analytics/Classes/Integrations/SEGIntegrationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ @interface SEGIntegrationsManager ()
@property (nonatomic, strong) NSArray *factories;
@property (nonatomic, strong) NSMutableDictionary *integrations;
@property (nonatomic, strong) NSMutableDictionary *registeredIntegrations;
@property (nonatomic, strong) NSMutableDictionary *integrationMiddleware;
@property (nonatomic) volatile BOOL initialized;
@property (nonatomic, copy) NSString *cachedAnonymousId;
@property (nonatomic, strong) SEGHTTPClient *httpClient;
Expand Down Expand Up @@ -105,6 +106,7 @@ - (instancetype _Nonnull)initWithAnalytics:(SEGAnalytics *_Nonnull)analytics
self.factories = [factories copy];
self.integrations = [NSMutableDictionary dictionaryWithCapacity:factories.count];
self.registeredIntegrations = [NSMutableDictionary dictionaryWithCapacity:factories.count];
self.integrationMiddleware = [NSMutableDictionary dictionaryWithCapacity:factories.count];

// Update settings on each integration immediately
[self refreshSettings];
Expand Down Expand Up @@ -188,38 +190,6 @@ - (void)identify:(SEGIdentifyPayload *)payload
options:payload.options
sync:false];
}
/*
- (void)identify:(NSString *)userId traits:(NSDictionary *)traits options:(NSDictionary *)options
{
NSCAssert2(userId.length > 0 || traits.count > 0, @"either userId (%@) or traits (%@) must be provided.", userId, traits);

NSDictionary *options;
if (p.anonymousId) {
NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:p.options];
mutableOptions[@"anonymousId"] = p.anonymousId;
options = [mutableOptions copy];
} else {
options = p.options;
}

NSString *anonymousId = [options objectForKey:@"anonymousId"];
if (anonymousId) {
[self saveAnonymousId:anonymousId];
} else {
anonymousId = self.cachedAnonymousId;
}

SEGIdentifyPayload *payload = [[SEGIdentifyPayload alloc] initWithUserId:userId
anonymousId:anonymousId
traits:SEGCoerceDictionary(traits)
context:SEGCoerceDictionary([options objectForKey:@"context"])
integrations:[options objectForKey:@"integrations"]];

[self callIntegrationsWithSelector:NSSelectorFromString(@"identify:")
arguments:@[ payload ]
options:options
sync:false];
}*/

#pragma mark - Track

Expand Down Expand Up @@ -383,6 +353,17 @@ - (void)setCachedSettings:(NSDictionary *)settings
[self updateIntegrationsWithSettings:settings[@"integrations"]];
}

- (nonnull NSArray<id<SEGMiddleware>> *)middlewareForIntegrationKey:(NSString *)key
{
NSMutableArray *result = [[NSMutableArray alloc] init];
for (SEGIntegrationMiddleware *container in self.configuration.integrationMiddleware) {
if ([container.integrationKey isEqualToString:key]) {
[result addObjectsFromArray:container.middleware];
}
}
return result;
}

- (void)updateIntegrationsWithSettings:(NSDictionary *)projectSettings
{
seg_dispatch_specific_sync(_serialQueue, ^{
Expand All @@ -397,6 +378,10 @@ - (void)updateIntegrationsWithSettings:(NSDictionary *)projectSettings
if (integration != nil) {
self.integrations[key] = integration;
self.registeredIntegrations[key] = @NO;

// setup integration middleware
NSArray<id<SEGMiddleware>> *middleware = [self middlewareForIntegrationKey:key];
self.integrationMiddleware[key] = [[SEGMiddlewareRunner alloc] initWithMiddleware:middleware];
}
[[NSNotificationCenter defaultCenter] postNotificationName:SEGAnalyticsIntegrationDidStart object:key userInfo:nil];
} else {
Expand Down Expand Up @@ -499,6 +484,49 @@ - (void)forwardSelector:(SEL)selector arguments:(NSArray *)arguments options:(NS
}];
}

/*
This kind of sucks, but we wrote ourselves into a corner here. A larger refactor will need to happen.
I also opted to not put this as a utility function because we shouldn't be doing this in the first place,
so consider it a one-off. If you find yourself needing to do this again, lets talk about a refactor.
*/
- (SEGEventType)eventTypeFromSelector:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
SEGEventType result = SEGEventTypeUndefined;

if ([selectorString hasPrefix:@"identify"]) {
result = SEGEventTypeIdentify;
} else if ([selectorString hasPrefix:@"track"]) {
result = SEGEventTypeTrack;
} else if ([selectorString hasPrefix:@"screen"]) {
result = SEGEventTypeScreen;
} else if ([selectorString hasPrefix:@"group"]) {
result = SEGEventTypeGroup;
} else if ([selectorString hasPrefix:@"alias"]) {
result = SEGEventTypeAlias;
} else if ([selectorString hasPrefix:@"reset"]) {
result = SEGEventTypeReset;
} else if ([selectorString hasPrefix:@"flush"]) {
result = SEGEventTypeFlush;
} else if ([selectorString hasPrefix:@"receivedRemoteNotification"]) {
result = SEGEventTypeReceivedRemoteNotification;
} else if ([selectorString hasPrefix:@"failedToRegisterForRemoteNotificationsWithError"]) {
result = SEGEventTypeFailedToRegisterForRemoteNotifications;
} else if ([selectorString hasPrefix:@"registeredForRemoteNotificationsWithDeviceToken"]) {
result = SEGEventTypeRegisteredForRemoteNotifications;
} else if ([selectorString hasPrefix:@"handleActionWithIdentifier"]) {
result = SEGEventTypeHandleActionWithForRemoteNotification;
} else if ([selectorString hasPrefix:@"continueUserActivity"]) {
result = SEGEventTypeContinueUserActivity;
} else if ([selectorString hasPrefix:@"openURL"]) {
result = SEGEventTypeOpenURL;
} else if ([selectorString hasPrefix:@"application"]) {
result = SEGEventTypeApplicationLifecycle;
}

return result;
}

- (void)invokeIntegration:(id<SEGIntegration>)integration key:(NSString *)key selector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options
{
if (![integration respondsToSelector:selector]) {
Expand All @@ -510,9 +538,9 @@ - (void)invokeIntegration:(id<SEGIntegration>)integration key:(NSString *)key se
SEGLog(@"Not sending call to %@ because it is disabled in options.", key);
return;
}

NSString *eventType = NSStringFromSelector(selector);
if ([eventType hasPrefix:@"track:"]) {
SEGEventType eventType = [self eventTypeFromSelector:selector];
if (eventType == SEGEventTypeTrack) {
SEGTrackPayload *eventPayload = arguments[0];
BOOL enabled = [[self class] isTrackEvent:eventPayload.event enabledForIntegration:key inPlan:self.cachedSettings[@"plan"]];
if (!enabled) {
Expand All @@ -521,8 +549,34 @@ - (void)invokeIntegration:(id<SEGIntegration>)integration key:(NSString *)key se
}
}

SEGLog(@"Running: %@ with arguments %@ on integration: %@", eventType, arguments, key);
NSInvocation *invocation = [self invocationForSelector:selector arguments:arguments];
NSMutableArray *newArguments = [arguments mutableCopy];

if (eventType != SEGEventTypeUndefined) {
SEGMiddlewareRunner *runner = self.integrationMiddleware[key];
if (runner.middlewares.count > 0) {
SEGPayload *payload = nil;
// things like flush have no args.
if (arguments.count > 0) {
payload = arguments[0];
}
SEGContext *context = [[[SEGContext alloc] initWithAnalytics:self.analytics] modify:^(id<SEGMutableContext> _Nonnull ctx) {
ctx.eventType = eventType;
ctx.payload = payload;
}];
NSLog(@"-------");
NSLog(@"contextIntegrationBefore = %@", context.payload);

context = [runner run:context callback:nil];
// if we weren't given args, don't set them.
if (arguments.count > 0) {
newArguments[0] = context.payload;
}
NSLog(@"contextIntegrationAfter = %@", context.payload);
}
}

SEGLog(@"Running: %@ with arguments %@ on integration: %@", NSStringFromSelector(selector), newArguments, key);
NSInvocation *invocation = [self invocationForSelector:selector arguments:newArguments];
[invocation invokeWithTarget:integration];
}

Expand Down Expand Up @@ -584,16 +638,6 @@ - (void)context:(SEGContext *)context next:(void (^_Nonnull)(SEGContext *_Nullab
case SEGEventTypeIdentify: {
SEGIdentifyPayload *p = (SEGIdentifyPayload *)context.payload;
[self identify:p];
/*
NSDictionary *options;
if (p.anonymousId) {
NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:p.options];
mutableOptions[@"anonymousId"] = p.anonymousId;
options = [mutableOptions copy];
} else {
options = p.options;
}
[self identify:p.userId traits:p.traits options:options];*/
break;
}
case SEGEventTypeTrack: {
Expand Down
2 changes: 1 addition & 1 deletion Analytics/Classes/Middlewares/SEGContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ @implementation SEGContext

- (instancetype)init
{
@throw [NSException exceptionWithName:@"Bad Initization"
@throw [NSException exceptionWithName:@"Bad Initialization"
reason:@"Please use initWithAnalytics:"
userInfo:nil];
}
Expand Down
11 changes: 9 additions & 2 deletions Analytics/Classes/Middlewares/SEGMiddleware.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ typedef void (^RunMiddlewaresCallback)(BOOL earlyExit, NSArray<id<SEGMiddleware>
// gonna support that for now to keep things simple. If there is a real need later we'll see then.
@property (nonnull, nonatomic, readonly) NSArray<id<SEGMiddleware>> *middlewares;

- (void)run:(SEGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback;
- (SEGContext * _Nonnull)run:(SEGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback;

- (instancetype _Nonnull)initWithMiddlewares:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares;
- (instancetype _Nonnull)initWithMiddleware:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares;

@end

// Container object for middlewares for a specific integration.
@interface SEGIntegrationMiddleware : NSObject
@property (nonatomic, strong, nonnull, readonly) NSString *integrationKey;
@property (nonatomic, strong, nullable, readonly) NSArray<id<SEGMiddleware>> *middleware;
- (instancetype _Nonnull)initWithKey:(NSString * _Nonnull)integrationKey middleware:(NSArray<id<SEGMiddleware>> * _Nonnull)middleware;
@end
34 changes: 26 additions & 8 deletions Analytics/Classes/Middlewares/SEGMiddleware.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
#import "SEGMiddleware.h"


@implementation SEGIntegrationMiddleware
- (instancetype)initWithKey:(NSString *)integrationKey middleware:(NSArray<id<SEGMiddleware>> *)middleware
{
if (self = [super init]) {
_integrationKey = integrationKey;
_middleware = middleware;
}
return self;
}
@end

@implementation SEGBlockMiddleware

- (instancetype)initWithBlock:(SEGMiddlewareBlock)block
Expand All @@ -30,37 +41,44 @@ - (void)context:(SEGContext *)context next:(SEGMiddlewareNext)next

@implementation SEGMiddlewareRunner

- (instancetype)initWithMiddlewares:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares
- (instancetype)initWithMiddleware:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares
{
if (self = [super init]) {
_middlewares = middlewares;
}
return self;
}

- (void)run:(SEGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback
- (SEGContext *)run:(SEGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback
{
[self runMiddlewares:self.middlewares context:context callback:callback];
return [self runMiddlewares:self.middlewares context:context callback:callback];
}

// TODO: Maybe rename SEGContext to SEGEvent to be a bit more clear?
// We could also use some sanity check / other types of logging here.
- (void)runMiddlewares:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares
- (SEGContext *)runMiddlewares:(NSArray<id<SEGMiddleware>> *_Nonnull)middlewares
context:(SEGContext *_Nonnull)context
callback:(RunMiddlewaresCallback _Nullable)callback
{
__block SEGContext * _Nonnull result = context;

BOOL earlyExit = context == nil;
if (middlewares.count == 0 || earlyExit) {
if (callback) {
callback(earlyExit, middlewares);
}
return;
return context;
}

[middlewares[0] context:context next:^(SEGContext *_Nullable newContext) {

NSLog(@"contextA = %@", [result.payload description]);
[middlewares[0] context:result next:^(SEGContext *_Nullable newContext) {
NSArray *remainingMiddlewares = [middlewares subarrayWithRange:NSMakeRange(1, middlewares.count - 1)];
[self runMiddlewares:remainingMiddlewares context:newContext callback:callback];
NSLog(@"contextBefore = %@", [result.payload description]);
result = [self runMiddlewares:remainingMiddlewares context:newContext callback:callback];
NSLog(@"contextAfter = %@", [result.payload description]);
}];

return result;
}

@end
Loading