diff --git a/Adjust.podspec b/Adjust.podspec index 8d6c583f0..5467deb9d 100644 --- a/Adjust.podspec +++ b/Adjust.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = "Adjust" - s.version = "4.0.7" + s.version = "4.0.8" s.summary = "This is the iOS SDK of adjust. You can read more about it at http://adjust.com." s.homepage = "http://adjust.com" s.license = { :type => 'MIT', :file => 'MIT-LICENSE' } s.author = { "Christian Wellenbrock" => "welle@adjust.com" } - s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.0.7" } + s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.0.8" } s.platform = :ios, '4.3' s.framework = 'SystemConfiguration' s.weak_framework = 'AdSupport', 'iAd' diff --git a/Adjust/ADJActivityPackage.m b/Adjust/ADJActivityPackage.m index 0b7aaf9eb..a31869962 100644 --- a/Adjust/ADJActivityPackage.m +++ b/Adjust/ADJActivityPackage.m @@ -24,8 +24,11 @@ - (NSString *)extendedString { [builder appendFormat:@"ClientSdk: %@\n", self.clientSdk]; if (self.parameters != nil) { + NSArray * sortedKeys = [[self.parameters allKeys] sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + NSUInteger keyCount = [sortedKeys count]; [builder appendFormat:@"Parameters:"]; - for (NSString *key in self.parameters) { + for (int i = 0; i < keyCount; i++) { + NSString *key = (NSString*)[sortedKeys objectAtIndex:i]; NSString *value = [self.parameters objectForKey:key]; [builder appendFormat:@"\n\t\t%-22s %@", [key UTF8String], value]; } diff --git a/Adjust/ADJUtil.m b/Adjust/ADJUtil.m index 40fe851ac..93b41969f 100644 --- a/Adjust/ADJUtil.m +++ b/Adjust/ADJUtil.m @@ -16,7 +16,7 @@ #include static NSString * const kBaseUrl = @"https://app.adjust.com"; -static NSString * const kClientSdk = @"ios4.0.7"; +static NSString * const kClientSdk = @"ios4.0.8"; static NSString * const kDateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Z"; static NSDateFormatter *dateFormat; diff --git a/AdjustTests/ADJActivityHandlerTests.m b/AdjustTests/ADJActivityHandlerTests.m index f82a3b7cb..25bacebc3 100644 --- a/AdjustTests/ADJActivityHandlerTests.m +++ b/AdjustTests/ADJActivityHandlerTests.m @@ -121,7 +121,7 @@ - (void)testFirstRun ADJActivityPackage *activityPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[0]; // check the Sdk version is being tested - XCTAssertEqual(@"ios4.0.7", activityPackage.clientSdk, @"%@", activityPackage.extendedString); + XCTAssertEqual(@"ios4.0.8", activityPackage.clientSdk, @"%@", activityPackage.extendedString); // check the server url XCTAssertEqual(@"https://app.adjust.com", ADJUtil.baseUrl); diff --git a/README.md b/README.md index 66c50b7b0..665b79ae1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you're using [CocoaPods][cocoapods], you can add the following line to your `Podfile` and continue with [step 3](#step3): ```ruby -pod 'Adjust', :git => 'git://github.com/adjust/ios_sdk.git', :tag => 'v4.0.7' +pod 'Adjust', :git => 'git://github.com/adjust/ios_sdk.git', :tag => 'v4.0.8' ``` ### 1. Get the SDK diff --git a/VERSION b/VERSION index 43beb4001..a2cec7aff 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.0.7 +4.0.8 diff --git a/doc/criteo_plugin.md b/doc/criteo_plugin.md index 1625f58e7..53c12764d 100644 --- a/doc/criteo_plugin.md +++ b/doc/criteo_plugin.md @@ -11,14 +11,6 @@ to `Copy items if needed` and select the radio button to `Create groups`. Now you can integrate each of the different Criteo events, like in the following examples: -### View Homepage - -```objc -ADJEvent *event = [ADJEvent eventWithEventToken:@"{viewHomepageEventToken}"]; - -[Adjust trackEvent:event]; -``` - ### View Search ```objc @@ -38,12 +30,9 @@ ADJEvent *event = [ADJEvent eventWithEventToken:@"{viewSearchEventToken}"]; ADJEvent *event = [ADJEvent eventWithEventToken:@"{viewListingEventToken}"]; -ADJCriteoProduct *product1 = [ADJCriteoProduct productWithId:@"productId1" price:100.0 quantity:1]; -ADJCriteoProduct *product2 = [ADJCriteoProduct productWithId:@"productId2" price:77.7 quantity:3]; -ADJCriteoProduct *product3 = [ADJCriteoProduct productWithId:@"productId3" price:50 quantity:2]; -NSArray *products = @[product1, product2, product3]; +NSArray *productIds = @[@"productId1", @"productId2", @"product3"]; -[ADJCriteo injectViewListingIntoEvent:event products:products customerId:@"customerId1"]; +[ADJCriteo injectViewListingIntoEvent:event productIds:productIds customerId:@"customerId1"]; [Adjust trackEvent:event]; ``` @@ -93,3 +82,76 @@ NSArray *products = @[product1, product2, product3]; [Adjust trackEvent:event]; ``` + +### User Level + +```objc +#import "ADJCriteo.h" + +ADJEvent *event = [ADJEvent eventWithEventToken:@"{userLevelEventToken}"]; + +[ADJCriteo injectUserLevelIntoEvent:event uiLevel:1 customerId:@"customerId1"]; + +[Adjust trackEvent:event]; +``` + +### User Status + +```objc +#import "ADJCriteo.h" + +ADJEvent *event = [ADJEvent eventWithEventToken:@"{userStatusEventToken}"]; + +[ADJCriteo injectUserStatusIntoEvent:event uiStatus:@"uiStatusValue" customerId:@"customerId1"]; + +[Adjust trackEvent:event]; +``` + +### Achievement Unlocked + +```objc +#import "ADJCriteo.h" + +ADJEvent *event = [ADJEvent eventWithEventToken:@"{achievementUnlockedEventToken}"]; + +[ADJCriteo injectAchievementUnlockedIntoEvent:event uiAchievement:@"uiAchievementValue" customerId:@"customerId"]; + +[Adjust trackEvent:event]; +``` + +### Custom Event + +```objc +#import "ADJCriteo.h" + +ADJEvent *event = [ADJEvent eventWithEventToken:@"{customEventEventToken}"]; + +[ADJCriteo injectCustomEventIntoEvent:event uiData:@"uiDataValue" customerId:@"customerId"]; + +[Adjust trackEvent:event]; +``` + +### Custom Event 2 + +```objc +#import "ADJCriteo.h" + +ADJEvent *event = [ADJEvent eventWithEventToken:@"{customEvent2EventToken}"]; + +[ADJCriteo injectCustomEvent2IntoEvent:event uiData2:@"uiDataValue2" uiData3:3 customerId:@"customerId"]; + +[Adjust trackEvent:event]; +``` + +### Hashed Email + +It's possible to attach an hashed email in every Criteo event with the `injectHashedEmailIntoCriteoEvents` method. +The hashed email will be sent with every Criteo event for the duration of the application lifecycle, +so it must be set again when the app is re-lauched. +The hashed email can be removed by setting the `injectHashedEmailIntoCriteoEvents` method with `nil`. + +```objc +#import "ADJCriteo.h" + +AdjustCriteo.injectHashedEmailIntoCriteoEvents("8455938a1db5c475a87d76edacb6284e"); +``` diff --git a/doc/migrate.md b/doc/migrate.md index 6c070ec1e..f0ed1e81c 100644 --- a/doc/migrate.md +++ b/doc/migrate.md @@ -1,4 +1,4 @@ -## Migrate your adjust SDK for iOS to v4.0.7 from v3.4.0 +## Migrate your adjust SDK for iOS to v4.0.8 from v3.4.0 ### Initial setup diff --git a/plugin/ADJCriteo.h b/plugin/ADJCriteo.h index cbe31333c..c948df0b9 100644 --- a/plugin/ADJCriteo.h +++ b/plugin/ADJCriteo.h @@ -32,7 +32,7 @@ checkOutDate:(NSString *)dout; + (void)injectViewListingIntoEvent:(ADJEvent *)event - products:(NSArray *)products + productIds:(NSArray *)productIds customerId:(NSString *)customerId; + (void)injectViewProductIntoEvent:(ADJEvent *)event @@ -47,4 +47,26 @@ products:(NSArray *)products customerId:(NSString *)customerId; ++ (void)injectUserLevelIntoEvent:(ADJEvent *)event + uiLevel:(NSUInteger)uiLevel + customerId:(NSString *)customerId; + ++ (void)injectUserStatusIntoEvent:(ADJEvent *)event + uiStatus:(NSString *)uiStatus + customerId:(NSString *)customerId; + ++ (void)injectAchievementUnlockedIntoEvent:(ADJEvent *)event + uiAchievement:(NSString *)uiAchievement + customerId:(NSString *)customerId; + ++ (void)injectCustomEventIntoEvent:(ADJEvent *)event + uiData:(NSString *)uiData + customerId:(NSString *)customerId; + ++ (void)injectCustomEvent2IntoEvent:(ADJEvent *)event + uiData2:(NSString *)uiData2 + uiData3:(NSUInteger)uiData3 + customerId:(NSString *)customerId; + ++ (void)injectHashedEmailIntoCriteoEvents:(NSString *)hashEmail; @end diff --git a/plugin/ADJCriteo.m b/plugin/ADJCriteo.m index 1c2e89e7b..c98fb083e 100644 --- a/plugin/ADJCriteo.m +++ b/plugin/ADJCriteo.m @@ -8,6 +8,9 @@ #import "ADJCriteo.h" #import "Adjust.h" +#import "ADJAdjustFactory.h" + +static const NSUInteger MAX_VIEW_LISTING_PRODUCTS = 3; @implementation ADJCriteoProduct @@ -36,6 +39,18 @@ + (ADJCriteoProduct *) productWithId:(NSString *)productId @implementation ADJCriteo +static NSString * hashEmailInternal; + ++ (id) logger { + return ADJAdjustFactory.logger; +} + ++ (void)injectHashEmail:(ADJEvent *)event { + if (hashEmailInternal == nil) { + return; + } + [event addPartnerParameter:@"criteo_email_hash" value:hashEmailInternal]; +} + (void)injectViewSearchIntoEvent:(ADJEvent *)event checkInDate:(NSString *)din @@ -43,16 +58,20 @@ + (void)injectViewSearchIntoEvent:(ADJEvent *)event { [event addPartnerParameter:@"din" value:din]; [event addPartnerParameter:@"dout" value:dout]; + + [ADJCriteo injectHashEmail:event]; } + (void)injectViewListingIntoEvent:(ADJEvent *)event - products:(NSArray *)products + productIds:(NSArray *)productIds customerId:(NSString *)customerId { [event addPartnerParameter:@"customer_id" value:customerId]; - NSString * jsonProducts = [ADJCriteo createCriteoVLFromProducts:products]; - [event addPartnerParameter:@"criteo_p" value:jsonProducts]; + NSString * jsonProductsIds = [ADJCriteo createCriteoVLFromProducts:productIds]; + [event addPartnerParameter:@"criteo_p" value:jsonProductsIds]; + + [ADJCriteo injectHashEmail:event]; } + (void)injectViewProductIntoEvent:(ADJEvent *)event @@ -61,6 +80,8 @@ + (void)injectViewProductIntoEvent:(ADJEvent *)event { [event addPartnerParameter:@"customer_id" value:customerId]; [event addPartnerParameter:@"criteo_p" value:productId]; + + [ADJCriteo injectHashEmail:event]; } + (void)injectCartIntoEvent:(ADJEvent *)event @@ -71,6 +92,8 @@ + (void)injectCartIntoEvent:(ADJEvent *)event NSString * jsonProducts = [ADJCriteo createCriteoVBFromProducts:products]; [event addPartnerParameter:@"criteo_p" value:jsonProducts]; + + [ADJCriteo injectHashEmail:event]; } + (void)injectTransactionConfirmedIntoEvent:(ADJEvent *)event @@ -81,101 +104,150 @@ + (void)injectTransactionConfirmedIntoEvent:(ADJEvent *)event NSString * jsonProducts = [ADJCriteo createCriteoVBFromProducts:products]; [event addPartnerParameter:@"criteo_p" value:jsonProducts]; + + [ADJCriteo injectHashEmail:event]; } -+ (NSString*) createCriteoVBFromProducts:(NSArray*) products ++ (void)injectUserLevelIntoEvent:(ADJEvent *)event + uiLevel:(NSUInteger)uiLevel + customerId:(NSString *)customerId { - NSMutableString* criteoVBValue = [NSMutableString stringWithString:@"["]; - for (ADJCriteoProduct *product in products) - { - NSString* productString = [NSString stringWithFormat:@"{\"i\":\"%@\",\"pr\":%f,\"q\":%lu}", - [product criteoProductID], - [product criteoPrice], - (unsigned long)[product criteoQuantity]]; + [event addPartnerParameter:@"customer_id" value:customerId]; - [criteoVBValue appendString:productString]; - if (product != [products lastObject]) - { - [criteoVBValue appendString:@","]; - } - } - [criteoVBValue appendString:@"]"]; - return [criteoVBValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString * uiLevelString = [NSString stringWithFormat:@"%lu",(unsigned long)uiLevel]; + [event addPartnerParameter:@"ui_level" value:uiLevelString]; + + [ADJCriteo injectHashEmail:event]; } -+ (NSString*) createCriteoVLFromProducts:(NSArray*) products ++ (void)injectUserStatusIntoEvent:(ADJEvent *)event + uiStatus:(NSString *)uiStatus + customerId:(NSString *)customerId { -#ifdef DEBUG - if ([products count] > 3) - NSLog(@"Warning : VL Events should only have at most 3 objects, discarding the rest"); -#endif - NSUInteger numberOfProducts = 0; - NSMutableString* criteoVBValue = [NSMutableString stringWithString:@"["]; + [event addPartnerParameter:@"customer_id" value:customerId]; + [event addPartnerParameter:@"ui_status" value:uiStatus]; - for (ADJCriteoProduct *product in products) - { - NSString* productString = [NSString stringWithFormat:@"\"%@\"", [product criteoProductID]]; + [ADJCriteo injectHashEmail:event]; +} - [criteoVBValue appendString:productString]; - ++numberOfProducts; ++ (void)injectAchievementUnlockedIntoEvent:(ADJEvent *)event + uiAchievement:(NSString *)uiAchievement + customerId:(NSString *)customerId +{ + [event addPartnerParameter:@"customer_id" value:customerId]; + [event addPartnerParameter:@"ui_achievmnt" value:uiAchievement]; - if (product != [products lastObject] && numberOfProducts < 3) - { - [criteoVBValue appendString:@","]; - } - if (numberOfProducts >= 3) - break; - } - [criteoVBValue appendString:@"]"]; - return [criteoVBValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + [ADJCriteo injectHashEmail:event]; } -+ (NSString*) createCriteoVBFromProductsDictionary:(NSArray*) products ++ (void)injectCustomEventIntoEvent:(ADJEvent *)event + uiData:(NSString *)uiData + customerId:(NSString *)customerId { + [event addPartnerParameter:@"customer_id" value:customerId]; + [event addPartnerParameter:@"ui_data" value:uiData]; + + [ADJCriteo injectHashEmail:event]; +} + ++ (void)injectCustomEvent2IntoEvent:(ADJEvent *)event + uiData2:(NSString *)uiData2 + uiData3:(NSUInteger)uiData3 + customerId:(NSString *)customerId +{ + [event addPartnerParameter:@"customer_id" value:customerId]; + [event addPartnerParameter:@"ui_data2" value:uiData2]; + + NSString * uiData3String = [NSString stringWithFormat:@"%lu",(unsigned long)uiData3]; + [event addPartnerParameter:@"ui_data3" value:uiData3String]; + + [ADJCriteo injectHashEmail:event]; +} + ++ (void)injectHashedEmailIntoCriteoEvents:(NSString *)hashEmail +{ + hashEmailInternal = hashEmail; +} + ++ (NSString*) createCriteoVBFromProducts:(NSArray*) products +{ + if (products == nil) { + [self.logger warn:@"Criteo Event product list is nil. It will sent as empty."]; + products = @[]; + } + + NSUInteger productsCount = [products count]; + NSMutableString* criteoVBValue = [NSMutableString stringWithString:@"["]; - for (NSDictionary* product in products) + for (NSUInteger i = 0; i < productsCount;) { + id productAtIndex = [products objectAtIndex:i]; + if (![productAtIndex isKindOfClass:[ADJCriteoProduct class]]) { + [self.logger error:@"Criteo Event should contain a list of ADJCriteoProduct"]; + return nil; + } + ADJCriteoProduct *product = (ADJCriteoProduct *)productAtIndex; NSString* productString = [NSString stringWithFormat:@"{\"i\":\"%@\",\"pr\":%f,\"q\":%lu}", - [product objectForKey:@"productID"], - [[product objectForKey:@"price"] floatValue], - [[product objectForKey:@"quantity"] integerValue]]; + [product criteoProductID], + [product criteoPrice], + (unsigned long)[product criteoQuantity]]; [criteoVBValue appendString:productString]; - if (product != [products lastObject]) + + i++; + + if (i == productsCount) { - [criteoVBValue appendString:@","]; + break; } + + [criteoVBValue appendString:@","]; } [criteoVBValue appendString:@"]"]; return [criteoVBValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } -+ (NSString*) createCriteoVLFromProductsArray:(NSArray*) products ++ (NSString*) createCriteoVLFromProducts:(NSArray*) productIds { -#ifdef DEBUG - if ([products count] > 3) - NSLog(@"Warning : VL Events should only have at most 3 objects, discarding the rest"); -#endif - NSUInteger numberOfProducts = 0; + if (productIds == nil) { + [self.logger warn:@"Criteo View Listing product ids list is nil. It will sent as empty."]; + productIds = @[]; + } + NSUInteger productsIdCount = [productIds count]; + if (productsIdCount > MAX_VIEW_LISTING_PRODUCTS) { + [self.logger warn:@"Criteo View Listing should only have at most 3 product ids. The rest will be discarded."]; + } - NSMutableString* criteoVBValue = [NSMutableString stringWithString:@"["]; - for (NSString* product in products) - { - NSString* productString = [NSString stringWithFormat:@"\"%@\"", product]; + NSMutableString* criteoVLValue = [NSMutableString stringWithString:@"["]; + + for (NSUInteger i = 0; i < productsIdCount;) { + id productAtIndex = [productIds objectAtIndex:i]; + NSString* productId; + if ([productAtIndex isKindOfClass:[NSString class]]) { + productId = productAtIndex; + } else if ([productAtIndex isKindOfClass:[ADJCriteoProduct class]]) { + ADJCriteoProduct * criteoProduct = (ADJCriteoProduct *)productAtIndex; + productId = [criteoProduct criteoProductID]; + [self.logger warn:@"Criteo View Listing should contain a list of product ids, not of ADJCriteoProduct. Reading the product id of the ADJCriteoProduct."]; + } else { + return nil; + } - [criteoVBValue appendString:productString]; - ++numberOfProducts; + NSString * productIdEscaped = [NSString stringWithFormat:@"\"%@\"", productId]; - if (product != [products lastObject] && numberOfProducts < 3) - { - [criteoVBValue appendString:@","]; - } - if (numberOfProducts >= 3) + [criteoVLValue appendString:productIdEscaped]; + + i++; + + if (i == productsIdCount || i >= MAX_VIEW_LISTING_PRODUCTS) { break; + } + [criteoVLValue appendString:@","]; } - [criteoVBValue appendString:@"]"]; - return [criteoVBValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + [criteoVLValue appendString:@"]"]; + return [criteoVLValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } @end