Skip to content
This repository was archived by the owner on Jul 22, 2020. It is now read-only.

Commit 2787135

Browse files
committed
nicer parsing of Iterable notification metadata (#18)
* update README for ghost push/uninstall tracking * add IterableNotificationMetadata class to represent metadata; only track push open if it comes from a real campaign * update changelog * add tests; fix isProof + isTestPush * update README * campaignId isn't always present in proofs
1 parent 5998f7c commit 2787135

File tree

7 files changed

+419
-12
lines changed

7 files changed

+419
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55
## [Unreleased]
66
#### Added
77
- completion handler blocks for all the Iterable APIs
8+
- class to represent Iterable notification metadata
89

910
#### Removed
1011
- nothing yet
1112

1213
#### Changed
13-
- nothing yet
14+
- no longer tracks push opens from test pushes, proofs
1415

1516
#### Fixed
16-
- nothing yet
17+
- no longer tracks push opens from ghost pushes
1718

1819

1920
## [2.0.1](https://github.com/Iterable/iterable-ios-sdk/releases/tag/2.0.1)

Iterable-iOS-SDK.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
094995471A8588400047E59B /* NSData+Conversion.m in Sources */ = {isa = PBXBuildFile; fileRef = 094995461A8588400047E59B /* NSData+Conversion.m */; };
1515
0995DBDD1CFFF7FE00D50F19 /* IterableLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0995DBDC1CFFF7FE00D50F19 /* IterableLogging.m */; };
1616
09C0FC071A1D6D4700D5A86B /* libIterable-iOS-SDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 09C0FBFB1A1D6D4700D5A86B /* libIterable-iOS-SDK.a */; };
17+
09CCB9301D07A7EA003EB75D /* IterableNotificationMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 09CCB92F1D07A7EA003EB75D /* IterableNotificationMetadata.m */; };
18+
09CCB9321D07BA04003EB75D /* IterableNotificationMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 09CCB9311D07BA04003EB75D /* IterableNotificationMetadataTests.m */; };
1719
6804B8601AC0F363000A126B /* CommerceItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 6804B85F1AC0F363000A126B /* CommerceItem.m */; };
1820
6845F7331AC3831B0043629D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6845F7321AC3831B0043629D /* SystemConfiguration.framework */; };
1921
68E32EF61AC21D37000CEBE1 /* CommerceItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 6804B8651AC10426000A126B /* CommerceItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -57,6 +59,9 @@
5759
09C0FBFB1A1D6D4700D5A86B /* libIterable-iOS-SDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libIterable-iOS-SDK.a"; sourceTree = BUILT_PRODUCTS_DIR; };
5860
09C0FC061A1D6D4700D5A86B /* Iterable-iOS-SDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Iterable-iOS-SDKTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
5961
09C0FC0C1A1D6D4700D5A86B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
62+
09CCB92D1D07A2E9003EB75D /* IterableNotificationMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IterableNotificationMetadata.h; sourceTree = "<group>"; };
63+
09CCB92F1D07A7EA003EB75D /* IterableNotificationMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IterableNotificationMetadata.m; sourceTree = "<group>"; };
64+
09CCB9311D07BA04003EB75D /* IterableNotificationMetadataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IterableNotificationMetadataTests.m; sourceTree = "<group>"; };
6065
27BFDE60C2FA15EB7F812F46 /* libPods-Iterable-iOS-SDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Iterable-iOS-SDK.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6166
50B627639A91F8FD17DE5BDD /* Pods-Iterable-iOS-SDKTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Iterable-iOS-SDKTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Iterable-iOS-SDKTests/Pods-Iterable-iOS-SDKTests.debug.xcconfig"; sourceTree = "<group>"; };
6267
6804B85F1AC0F363000A126B /* CommerceItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommerceItem.m; sourceTree = "<group>"; };
@@ -121,6 +126,8 @@
121126
094995461A8588400047E59B /* NSData+Conversion.m */,
122127
0995DBDB1CFFF70800D50F19 /* IterableLogging.h */,
123128
0995DBDC1CFFF7FE00D50F19 /* IterableLogging.m */,
129+
09CCB92D1D07A2E9003EB75D /* IterableNotificationMetadata.h */,
130+
09CCB92F1D07A7EA003EB75D /* IterableNotificationMetadata.m */,
124131
);
125132
path = "Iterable-iOS-SDK";
126133
sourceTree = "<group>";
@@ -131,6 +138,7 @@
131138
09C0FC0B1A1D6D4700D5A86B /* Supporting Files */,
132139
091489E11CF6986900482D82 /* IterableAPITests.m */,
133140
091489E71CF6AC1800482D82 /* CommerceItemTests.m */,
141+
09CCB9311D07BA04003EB75D /* IterableNotificationMetadataTests.m */,
134142
);
135143
path = "Iterable-iOS-SDKTests";
136144
sourceTree = "<group>";
@@ -364,6 +372,7 @@
364372
isa = PBXSourcesBuildPhase;
365373
buildActionMask = 2147483647;
366374
files = (
375+
09CCB9301D07A7EA003EB75D /* IterableNotificationMetadata.m in Sources */,
367376
094995471A8588400047E59B /* NSData+Conversion.m in Sources */,
368377
093734271A1D701F00C950B6 /* IterableAPI.m in Sources */,
369378
6804B8601AC0F363000A126B /* CommerceItem.m in Sources */,
@@ -376,6 +385,7 @@
376385
buildActionMask = 2147483647;
377386
files = (
378387
091489E21CF6986900482D82 /* IterableAPITests.m in Sources */,
388+
09CCB9321D07BA04003EB75D /* IterableNotificationMetadataTests.m in Sources */,
379389
091489E81CF6AC1800482D82 /* CommerceItemTests.m in Sources */,
380390
);
381391
runOnlyForDeploymentPostprocessing = 0;

Iterable-iOS-SDK/IterableAPI.m

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#import "NSData+Conversion.h"
2020
#import "CommerceItem.h"
2121
#import "IterableLogging.h"
22+
#import "IterableNotificationMetadata.h"
2223

2324
@interface IterableAPI () {
2425
}
@@ -249,6 +250,7 @@ + (OnFailureHandler)defaultOnFailure:(NSString *)identifier
249250
};
250251
}
251252

253+
252254
//////////////////////////////////////////////////////////////
253255
/// @name Implementations of things documents in IterableAPI.h
254256
//////////////////////////////////////////////////////////////
@@ -393,16 +395,12 @@ - (void)trackPushOpen:(NSDictionary *)userInfo dataFields:(NSDictionary *)dataFi
393395
// documented in IterableAPI.h
394396
- (void)trackPushOpen:(NSDictionary *)userInfo dataFields:(NSDictionary *)dataFields onSuccess:(OnSuccessHandler)onSuccess onFailure:(OnFailureHandler)onFailure
395397
{
396-
LogDebug(@"tracking push open");
397-
398-
if (userInfo && userInfo[@"itbl"]) {
399-
NSDictionary *pushData = userInfo[@"itbl"];
400-
if ([pushData isKindOfClass:[NSDictionary class]] && pushData[@"campaignId"] && pushData[@"templateId"]) {
401-
[self trackPushOpen:pushData[@"campaignId"] templateId:pushData[@"templateId"] appAlreadyRunning:false dataFields:dataFields onSuccess:onSuccess onFailure:onFailure];
402-
} else {
403-
if (onFailure) {
404-
onFailure(@"Not tracking push open - payload is not an Iterable notification", [[NSData alloc] init]);
405-
}
398+
IterableNotificationMetadata *notification = [IterableNotificationMetadata metadataFromLaunchOptions:userInfo];
399+
if (notification && [notification isRealCampaignNotification]) {
400+
[self trackPushOpen:notification.campaignId templateId:notification.templateId appAlreadyRunning:false dataFields:dataFields onSuccess:onSuccess onFailure:onFailure];
401+
} else {
402+
if (onFailure) {
403+
onFailure(@"Not tracking push open - payload is not an Iterable notification, or a test/proof/ghost push", [[NSData alloc] init]);
406404
}
407405
}
408406
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// IterableNotification.h
3+
// Iterable-iOS-SDK
4+
//
5+
// Created by Ilya Brin on 6/7/16.
6+
// Copyright © 2016 Iterable. All rights reserved.
7+
//
8+
9+
@import Foundation;
10+
11+
// all params are nonnull, unless annotated otherwise
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
/**
15+
`IterableNotificationMetadata` represents the metadata in an Iterable push notification
16+
*/
17+
@interface IterableNotificationMetadata : NSObject
18+
19+
////////////////////
20+
/// @name Properties
21+
////////////////////
22+
23+
/**
24+
The campaignId of this notification
25+
*/
26+
@property(nonatomic, readonly, copy) NSNumber *campaignId;
27+
28+
/**
29+
The templateId of this notification
30+
*/
31+
@property(nonatomic, readonly, copy) NSNumber *templateId;
32+
33+
/**
34+
Whether this notification is a ghost push
35+
*/
36+
@property(nonatomic, readonly) BOOL isGhostPush;
37+
38+
//////////////////////////////////////////////////
39+
/// @name Creating an IterableNotificationMetadata
40+
//////////////////////////////////////////////////
41+
42+
/**
43+
@method
44+
45+
@abstract Creates an `IterableNotificationMetadata` from a push payload
46+
47+
@param userInfo The notification payload
48+
49+
@return an instance of `IterableNotificationMetadata` with the specified properties; `nil` if this isn't an Iterable notification
50+
51+
@warning `metadataFromLaunchOptions` will return `nil` if `userInfo` isn't an Iterable notification
52+
*/
53+
+ (nullable instancetype)metadataFromLaunchOptions:(NSDictionary *)userInfo;
54+
55+
///////////////////////////
56+
/// @name Utility functions
57+
///////////////////////////
58+
59+
/**
60+
@method
61+
62+
@return `YES` if this push is a `proof` push; `NO` otherwise
63+
*/
64+
- (BOOL)isProof;
65+
66+
/**
67+
@method
68+
69+
@return `YES` if this is a test push; `NO` otherwise
70+
*/
71+
- (BOOL)isTestPush;
72+
73+
/**
74+
@method
75+
76+
@return `YES` if this is a non-ghost, non-proof, non-test real send; `NO` otherwise
77+
*/
78+
- (BOOL)isRealCampaignNotification;
79+
80+
@end
81+
82+
NS_ASSUME_NONNULL_END
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//
2+
// IterableNotification.m
3+
// Iterable-iOS-SDK
4+
//
5+
// Created by Ilya Brin on 6/7/16.
6+
// Copyright © 2016 Iterable. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
#import "IterableNotificationMetadata.h"
12+
13+
@implementation IterableNotificationMetadata
14+
15+
//////////////////////////
16+
/// @name String constants
17+
//////////////////////////
18+
19+
static NSString *const MetadataField = @"itbl";
20+
static NSString *const CampaignIdField = @"campaignId";
21+
static NSString *const TemplateIdField = @"templateId";
22+
static NSString *const IsGhostPushField = @"isGhostPush";
23+
24+
//////////////////////////
25+
/// @name Internal methods
26+
//////////////////////////
27+
28+
/**
29+
@method
30+
31+
@abstract Checks if a push notification originated from Iterable
32+
33+
@param userInfo The notification payload
34+
35+
@return `YES` if the notification is from Iterable; `NO` otherwise
36+
*/
37+
+ (BOOL)isIterableNotification:(NSDictionary *)userInfo
38+
{
39+
if (userInfo && userInfo[MetadataField]) {
40+
id pushData = userInfo[MetadataField];
41+
return [pushData isKindOfClass:[NSDictionary class]]
42+
&& (pushData[CampaignIdField] ? [pushData[CampaignIdField] isKindOfClass:[NSNumber class]] : YES) // campaignId doesn't have to be there (because of proofs)
43+
&& [pushData[TemplateIdField] isKindOfClass:[NSNumber class]]
44+
&& [pushData[IsGhostPushField] isKindOfClass:[NSNumber class]];
45+
} else {
46+
return NO;
47+
}
48+
}
49+
50+
/**
51+
@method
52+
53+
@abstract Creates an `IterableNotificationMetadata` from a push payload
54+
55+
@param userInfo The notification payload
56+
57+
@return An instance of `IterableNotificationMetadata` with the specified properties
58+
59+
@warning This method assumes that `userInfo` is an Iterable notification (via `isIterableNotification` check beforehand)
60+
*/
61+
- (instancetype)initFromLaunchOptions:(NSDictionary *)userInfo
62+
{
63+
if (self = [super init]) {
64+
NSDictionary *pushData = userInfo[MetadataField];
65+
_campaignId = pushData[CampaignIdField] ? pushData[CampaignIdField] : @0;
66+
_templateId = pushData[TemplateIdField];
67+
_isGhostPush = [pushData[IsGhostPushField] boolValue];
68+
}
69+
return self;
70+
}
71+
72+
////////////////////////
73+
/// @name Implementation
74+
////////////////////////
75+
76+
// documented in IterableNotification.h
77+
+ (instancetype)metadataFromLaunchOptions:(NSDictionary *)userInfo
78+
{
79+
if ([IterableNotificationMetadata isIterableNotification:userInfo]) {
80+
return [[IterableNotificationMetadata alloc] initFromLaunchOptions:userInfo];
81+
} else {
82+
return nil;
83+
}
84+
}
85+
86+
// documented in IterableNotification.h
87+
- (BOOL)isProof {
88+
return _campaignId.integerValue == 0 && _templateId.integerValue != 0;
89+
}
90+
91+
// documented in IterableNotification.h
92+
- (BOOL)isTestPush {
93+
return _campaignId.integerValue == 0 && _templateId.integerValue == 0;
94+
}
95+
96+
// documented in IterableNotification.h
97+
- (BOOL)isRealCampaignNotification {
98+
return !(_isGhostPush || [self isProof] || [self isTestPush]);
99+
}
100+
101+
@end

0 commit comments

Comments
 (0)