Skip to content

Commit

Permalink
Open source FIAM headless SDK (#2312)
Browse files Browse the repository at this point in the history
  • Loading branch information
christibbs authored and paulb777 committed Feb 2, 2019
1 parent a43709f commit 3905bd2
Show file tree
Hide file tree
Showing 137 changed files with 15,462 additions and 0 deletions.
61 changes: 61 additions & 0 deletions Firebase/InAppMessaging/Analytics/FIRIAMAnalyticsEventLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>
#import "FIRIAMClientInfoFetcher.h"
#import "FIRIAMTimeFetcher.h"

NS_ASSUME_NONNULL_BEGIN

/// Values for different fiam activity types.
typedef NS_ENUM(NSInteger, FIRIAMAnalyticsLogEventType) {

FIRIAMAnalyticsLogEventUnknown = -1,

FIRIAMAnalyticsEventMessageImpression = 0,
FIRIAMAnalyticsEventActionURLFollow = 1,
FIRIAMAnalyticsEventMessageDismissAuto = 2,
FIRIAMAnalyticsEventMessageDismissClick = 3,
FIRIAMAnalyticsEventMessageDismissSwipe = 4,

// category: errors happened
FIRIAMAnalyticsEventImageFetchError = 11,
FIRIAMAnalyticsEventImageFormatUnsupported = 12,

FIRIAMAnalyticsEventFetchAPINetworkError = 13,
FIRIAMAnalyticsEventFetchAPIClientError = 14, // server returns 4xx status code
FIRIAMAnalyticsEventFetchAPIServerError = 15, // server returns 5xx status code

// Events for test messages
FIRIAMAnalyticsEventTestMessageImpression = 16,
FIRIAMAnalyticsEventTestMessageClick = 17,
};

// a protocol for collecting Analytics log records. It's implementation will decide
// what to do with that analytics log record
@protocol FIRIAMAnalyticsEventLogger
/**
* Adds an analytics log record.
* @param eventTimeInMs the timestamp in ms for when the event happened.
* if it's nil, the implementation will use the current system for this info.
*/
- (void)logAnalyticsEventForType:(FIRIAMAnalyticsLogEventType)eventType
forCampaignID:(NSString *)campaignID
withCampaignName:(NSString *)campaignName
eventTimeInMs:(nullable NSNumber *)eventTimeInMs
completion:(void (^)(BOOL success))completion;
@end
NS_ASSUME_NONNULL_END
45 changes: 45 additions & 0 deletions Firebase/InAppMessaging/Analytics/FIRIAMAnalyticsEventLoggerImpl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

#import "FIRIAMAnalyticsEventLogger.h"

@class FIRIAMClearcutLogger;
@protocol FIRIAMTimeFetcher;
@protocol FIRAnalyticsInterop;

NS_ASSUME_NONNULL_BEGIN
/**
* Implementation of protocol FIRIAMAnalyticsEventLogger by doing two things
* 1 Firing Firebase Analytics Events for impressions and clicks and dismisses
* 2 Making clearcut logging for all other types of analytics events
*/
@interface FIRIAMAnalyticsEventLoggerImpl : NSObject <FIRIAMAnalyticsEventLogger>
- (instancetype)init NS_UNAVAILABLE;

/**
*
* @param userDefaults needed for tracking upload timing info persistently.If nil, using
* NSUserDefaults standardUserDefaults. It's defined as a parameter to help with
* unit testing mocking
*/
- (instancetype)initWithClearcutLogger:(FIRIAMClearcutLogger *)ctLogger
usingTimeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
usingUserDefaults:(nullable NSUserDefaults *)userDefaults
analytics:(nullable id<FIRAnalyticsInterop>)analytics;
@end
NS_ASSUME_NONNULL_END
170 changes: 170 additions & 0 deletions Firebase/InAppMessaging/Analytics/FIRIAMAnalyticsEventLoggerImpl.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "FIRIAMAnalyticsEventLoggerImpl.h"

#import <FirebaseAnalyticsInterop/FIRAnalyticsInterop.h>
#import <FirebaseCore/FIRLogger.h>
#import "FIRCore+InAppMessaging.h"
#import "FIRIAMClearcutLogger.h"

typedef void (^FIRAUserPropertiesCallback)(NSDictionary *userProperties);

@interface FIRIAMAnalyticsEventLoggerImpl ()
@property(readonly, nonatomic) FIRIAMClearcutLogger *clearCutLogger;
@property(readonly, nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
@end

// in these kFAXX constants, FA represents FirebaseAnalytics
static NSString *const kFIREventOriginFIAM = @"fiam";
;
static NSString *const kFAEventNameForImpression = @"firebase_in_app_message_impression";
static NSString *const kFAEventNameForAction = @"firebase_in_app_message_action";
static NSString *const kFAEventNameForDismiss = @"firebase_in_app_message_dismiss";

// In order to support tracking conversions from clicking a fiam event, we need to set
// an analytics user property with the fiam message's campaign id.
// This is the user property as kFIRUserPropertyLastNotification defined for FCM.
// Unlike FCM, FIAM would only allow the user property to exist up to certain expiration time
// after which, we stop attributing any further conversions to that fiam message click.
// So we include kFAUserPropertyPrefixForFIAM as the prefix for the entry written by fiam SDK
// to avoid removing entries written by FCM SDK
static NSString *const kFAUserPropertyForLastNotification = @"_ln";
static NSString *const kFAUserPropertyPrefixForFIAM = @"fiam:";

// This user defaults key is for the entry to tell when we should remove the private user
// property from a prior action url click to stop conversion attribution for a campaign
static NSString *const kFIAMUserDefaualtsKeyForRemoveUserPropertyTimeInSeconds =
@"firebase-iam-conversion-tracking-expires-in-seconds";

@implementation FIRIAMAnalyticsEventLoggerImpl {
id<FIRAnalyticsInterop> _analytics;
}

- (instancetype)initWithClearcutLogger:(FIRIAMClearcutLogger *)ctLogger
usingTimeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
usingUserDefaults:(nullable NSUserDefaults *)userDefaults
analytics:(nullable id<FIRAnalyticsInterop>)analytics {
if (self = [super init]) {
_clearCutLogger = ctLogger;
_timeFetcher = timeFetcher;
_analytics = analytics;
_userDefaults = userDefaults ? userDefaults : [NSUserDefaults standardUserDefaults];

if (!_analytics) {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM280002",
@"Firebase In App Messaging was not configured with FirebaseAnalytics.");
}
}
return self;
}

- (NSDictionary *)constructFAEventParamsWithCampaignID:(NSString *)campaignID
campaignName:(NSString *)campaignName {
// event parameter names are aligned with definitions in event_names_util.cc
return @{
@"_nmn" : campaignName ?: @"unknown",
@"_nmid" : campaignID ?: @"unknown",
@"_ndt" : @([self.timeFetcher currentTimestampInSeconds])
};
}

- (void)logFAEventsForMessageImpressionWithcampaignID:(NSString *)campaignID
campaignName:(NSString *)campaignName {
if (_analytics) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM280001",
@"Log campaign impression Firebase Analytics event for campaign ID %@", campaignID);

NSDictionary *params = [self constructFAEventParamsWithCampaignID:campaignID
campaignName:campaignName];
[_analytics logEventWithOrigin:kFIREventOriginFIAM
name:kFAEventNameForImpression
parameters:params];
}
}

- (BOOL)setAnalyticsUserPropertyForKey:(NSString *)key withValue:(NSString *)value {
if (!_analytics || !key || !value) {
return NO;
}
[_analytics setUserPropertyWithOrigin:kFIREventOriginFIAM name:key value:value];
return YES;
}

- (void)logFAEventsForMessageActionWithCampaignID:(NSString *)campaignID
campaignName:(NSString *)campaignName {
if (_analytics) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM280004",
@"Log action click Firebase Analytics event for campaign ID %@", campaignID);

NSDictionary *params = [self constructFAEventParamsWithCampaignID:campaignID
campaignName:campaignName];

[_analytics logEventWithOrigin:kFIREventOriginFIAM
name:kFAEventNameForAction
parameters:params];
}

// set a special user property so that conversion events can be queried based on that
// for reporting purpose
NSString *conversionTrackingUserPropertyValue =
[NSString stringWithFormat:@"%@%@", kFAUserPropertyPrefixForFIAM, campaignID];

if ([self setAnalyticsUserPropertyForKey:kFAUserPropertyForLastNotification
withValue:conversionTrackingUserPropertyValue]) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM280009",
@"User property for conversion tracking was set for campaign %@", campaignID);
}
}

- (void)logFAEventsForMessageDismissWithcampaignID:(NSString *)campaignID
campaignName:(NSString *)campaignName {
if (_analytics) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM280007",
@"Log message dismiss Firebase Analytics event for campaign ID %@", campaignID);

NSDictionary *params = [self constructFAEventParamsWithCampaignID:campaignID
campaignName:campaignName];
[_analytics logEventWithOrigin:kFIREventOriginFIAM
name:kFAEventNameForDismiss
parameters:params];
}
}

- (void)logAnalyticsEventForType:(FIRIAMAnalyticsLogEventType)eventType
forCampaignID:(NSString *)campaignID
withCampaignName:(NSString *)campaignName
eventTimeInMs:(nullable NSNumber *)eventTimeInMs
completion:(void (^)(BOOL success))completion {
// log Firebase Analytics event first
if (eventType == FIRIAMAnalyticsEventMessageImpression) {
[self logFAEventsForMessageImpressionWithcampaignID:campaignID campaignName:campaignName];
} else if (eventType == FIRIAMAnalyticsEventActionURLFollow) {
[self logFAEventsForMessageActionWithCampaignID:campaignID campaignName:campaignName];
} else if (eventType == FIRIAMAnalyticsEventMessageDismissAuto ||
eventType == FIRIAMAnalyticsEventMessageDismissClick) {
[self logFAEventsForMessageDismissWithcampaignID:campaignID campaignName:campaignName];
}

// and do clearcut logging as well
[self.clearCutLogger logAnalyticsEventForType:eventType
forCampaignID:campaignID
withCampaignName:campaignName
eventTimeInMs:eventTimeInMs
completion:completion];
}
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

@class FIRIAMClearcutLogRecord;
@protocol FIRIAMTimeFetcher;

NS_ASSUME_NONNULL_BEGIN
// class for sending requests to clearcut over its http API
@interface FIRIAMClearcutHttpRequestSender : NSObject

/**
* Create an FIRIAMClearcutHttpRequestSender instance with specified clearcut server.
*
* @param serverHost API server host.
* @param osMajorVersion detected iOS major version of the current device
*/
- (instancetype)initWithClearcutHost:(NSString *)serverHost
usingTimeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
withOSMajorVersion:(NSString *)osMajorVersion;

/**
* Sends a batch of FIRIAMClearcutLogRecord records to clearcut server.
* @param logs an array of log records to be sent.
* @param completion is the handler to triggered upon completion. 'success' is a bool
* to indicate if the sending is successful. 'shouldRetryLogs' indicates if these
* logs need to be retried later on. On success case, waitTimeInMills is the value
* returned from clearcut server to indicate the minimal wait time before another
* send request can be attempted.
*/

- (void)sendClearcutHttpRequestForLogs:(NSArray<FIRIAMClearcutLogRecord *> *)logs
withCompletion:(void (^)(BOOL success,
BOOL shouldRetryLogs,
int64_t waitTimeInMills))completion;
@end
NS_ASSUME_NONNULL_END
Loading

0 comments on commit 3905bd2

Please sign in to comment.