Merge FLoC SDK into master branch. (#6466)

* Create Segmentation SDK structure for source and unit tests. (#3214)

* Create Segmentation SDK structure for source and unit tests.

* Review changes: Remove unnecessary files in test folder. Add md-floc-master to CI

* Rename Segmentation directory to FirebaseSegmentation directory. Update podspec to include search header path.

* Add core support with interop for Segmentation SDK.  (#3430)

* Add core support with interop for Segmentation SDK. Also update headers to be under sources folder.

* Review fixes.

* Minor changes.

* Fix style.

* Fix style.

* Style changes.

* Fix whitespace in travis.yml

* Fix style.

* Travis CI is stuck..try updating the travis.yml

* Undo travis.yml change.

* Working drop of Segmentation SDK along with test app and unit tests. (#4574)

* Working drop of Segmentation SDK along with sample app and unit tests.

* Update if_changed.sh to include FirebaseSegmentation.

* Complete NS_ASSUME_NON_NULL_START with NS_ASSUME_NON_NULL_END in header file.

* Fix unit tests.

* Fix style.

* Fixes after running XCode's static analyzer.

* Fix style.

* fix style.

* 'pod lib lint' fixes.

* Fix analyzer errors.

* Address review comments.

* Minor changes for review comments.

* Address review comments.

* Address review comments.

* stop mocking in tear down method for tests.

* Minor update to sample app project.

* Fix trailing whitespace in Podfile.

* Add set -x to check.sh

* update segmetation dependency version

* migrate FloC SDK to depend on FIS SDK directly

* format floc

* format

* using customized FIRapp

* remove test plist file

* refactor to capture weakself

* format

* replace partial mock with class mock

* minor refactoring

* use subscript to manipulate dictionary instance

* fix import error

* minor refoctoring, addressing comments

* address comments

* fix configurations

* format

Co-authored-by: dmandar <dmandar@users.noreply.github.com>
Co-authored-by: ChaoqunCHEN <cqchen93@gmail.com>
This commit is contained in:
authored and GitHub committed 2020-10-01 15:10:32 -07:00
1 parent dfffd85572
commit 69bfe3fd3c
34 files changed
+2477 -3

No files matched your search

+3
View File
@@ -29,6 +29,9 @@ FirebaseStorage/Tests/Integration/Credentials.h
FirebaseStorage/Tests/SwiftIntegration/Credentials.swift
FirebaseStorageSwift/Tests/Integration/Credentials.swift
# FirebaseSegmentation integration tests GoogleService-Info.plist
FirebaseSegmentation/Tests/Sample/GoogleService-Info.plist
Secrets.tar
# OS X
+4
View File
@@ -49,6 +49,7 @@ def labelsForModifiedFiles()
labels.push("api: instanceid") if @has_instanceid_changes
labels.push("api: messaging") if @has_messaging_changes
labels.push("api: remoteconfig") if @has_remoteconfig_changes
labels.push("api: segmentation") if @has_segmentation_changes
labels.push("api: storage") if @has_storage_changes
labels.push("GoogleDataTransport") if @has_gdt_changes
labels.push("GoogleUtilities") if @has_googleutilities_changes
@@ -102,6 +103,8 @@ has_license_changes = didModify(["LICENSE"])
@has_messaging_api_changes = hasChangesIn("FirebaseMessaging/Sources/Public/")
@has_remoteconfig_changes = hasChangesIn("FirebaseRemoteConfig/")
@has_remoteconfig_api_changes = hasChangesIn("FirebaseRemoteConfig/Sources/Public/")
@has_segmentation_changes = hasChangesIn("FirebaseSegmentation/")
@has_segmentation_api_changes = hasChangesIn("FirebaseSegmentation/Source/Public/
@has_storage_changes = hasChangesIn("FirebaseStorage/")
@has_storage_api_changes = hasChangesIn("FirebaseStorage/Sources/Public/")
@@ -125,6 +128,7 @@ has_license_changes = didModify(["LICENSE"])
@has_instanceid_api_changes ||
@has_messaging_api_changes ||
@has_remoteconfig_api_changes ||
@has_segmentation_api_changes ||
@has_storage_api_changes ||
@has_gdt_api_changes
+45
View File
@@ -0,0 +1,45 @@
Pod::Spec.new do |s|
s.name = 'FirebaseSegmentation'
s.version = '0.1.0'
s.summary = 'Firebase Segmentation SDK'
s.description = <<-DESC
Firebase Segmentation enables you to associate your custom application instance ID with Firebase for user segmentation.
DESC
s.homepage = 'https://firebase.google.com'
s.license = { :type => 'Apache', :file => 'LICENSE' }
s.authors = 'Google, Inc.'
s.source = {
:git => 'https://github.com/firebase/firebase-ios-sdk.git',
:tag => 'Segmentation-' + s.version.to_s
}
s.ios.deployment_target = '8.0'
s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
s.source_files = [
'FirebaseSegmentation/Sources/**/*.[mh]',
'FirebaseCore/Sources/Private/*.h',
]
s.public_header_files = 'FirebaseSegmentation/Sources/Public/*.h'
s.dependency 'FirebaseCore', '~> 6.7'
s.dependency 'FirebaseInstallations', '~> 1.7'
header_search_paths = {
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"'
}
s.pod_target_xcconfig = {
'GCC_C_LANGUAGE_STANDARD' => 'c99',
'GCC_PREPROCESSOR_DEFINITIONS' => 'FIRSegmentation_VERSION=' + s.version.to_s
}.merge(header_search_paths)
s.test_spec 'unit' do |unit_tests|
unit_tests.source_files = 'FirebaseSegmentation/Tests/Unit/*.[mh]'
unit_tests.dependency 'OCMock'
unit_tests.requires_app_host = true
end
end
Whitespace-only changes.
@@ -0,0 +1,78 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/Public/FIRSegmentation.h"
#import "FirebaseCore/Sources/Private/FIRComponentContainer.h"
#import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseSegmentation/Sources/Private/FIRSegmentationComponent.h"
#import "FirebaseSegmentation/Sources/SEGContentManager.h"
@implementation FIRSegmentation {
NSString *_firebaseAppName;
SEGContentManager *_contentManager;
}
+ (nonnull FIRSegmentation *)segmentation {
if (![FIRApp isDefaultAppConfigured]) {
[NSException
raise:kFirebaseSegmentationErrorDomain
format:@"FIRApp not configured. Please make sure you have called [FIRApp configure]"];
}
return [FIRSegmentation segmentationWithApp:[FIRApp defaultApp]];
}
+ (nonnull FIRSegmentation *)segmentationWithApp:(nonnull FIRApp *)firebaseApp {
// Use the provider to generate and return instances of FIRSegmentation for this specific app and
// namespace. This will ensure the app is configured before Remote Config can return an instance.
id<FIRSegmentationProvider> provider =
FIR_COMPONENT(FIRSegmentationProvider, firebaseApp.container);
return [provider segmentation];
}
- (void)setCustomInstallationID:(NSString *)customInstallationID
completion:(void (^)(NSError *))completionHandler {
[_contentManager
associateCustomInstallationIdentiferNamed:customInstallationID
firebaseApp:_firebaseAppName
completion:^(BOOL success, NSDictionary *result) {
if (!success) {
// TODO(dmandar) log; pass along internal error code.
NSError *error = [NSError
errorWithDomain:kFirebaseSegmentationErrorDomain
code:FIRSegmentationErrorCodeInternal
userInfo:result];
completionHandler(error);
} else {
completionHandler(nil);
}
}];
}
/// Designated initializer
- (instancetype)initWithAppName:(NSString *)appName FIROptions:(FIROptions *)options {
self = [super init];
if (self) {
_firebaseAppName = appName;
// Initialize the content manager.
_contentManager = [SEGContentManager sharedInstanceWithOptions:options];
}
return self;
}
@end
@@ -0,0 +1,107 @@
/*
* Copyright 2019 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 "FirebaseSegmentation/Sources/Private/FIRSegmentationComponent.h"
#import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "FirebaseCore/Sources/Private/FIRComponentContainer.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import "FirebaseSegmentation/Sources/Private/FIRSegmentationInternal.h"
#import "FirebaseSegmentation/Sources/SEGSegmentationConstants.h"
#ifndef FIRSegmentation_VERSION
#error "FIRSegmentation_VERSION is not defined: \
add -DFIRSegmentation_VERSION=... to the build invocation"
#endif
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
@implementation FIRSegmentationComponent
/// Default method for retrieving a Segmentation instance, or creating one if it doesn't exist.
- (FIRSegmentation *)segmentation {
// Validate the required information is available.
FIROptions *options = self.app.options;
NSString *errorPropertyName;
if (options.googleAppID.length == 0) {
errorPropertyName = @"googleAppID";
} else if (options.GCMSenderID.length == 0) {
errorPropertyName = @"GCMSenderID";
}
if (errorPropertyName) {
[NSException
raise:kFirebaseSegmentationErrorDomain
format:@"%@",
[NSString
stringWithFormat:
@"Firebase Segmentation is missing the required %@ property from the "
@"configured FirebaseApp and will not be able to function properly. Please "
@"fix this issue to ensure that Firebase is correctly configured.",
errorPropertyName]];
}
FIRSegmentation *instance = self.segmentationInstance;
if (!instance) {
instance = [[FIRSegmentation alloc] initWithAppName:self.app.name FIROptions:self.app.options];
self.segmentationInstance = instance;
}
return instance;
}
/// Default initializer.
- (instancetype)initWithApp:(FIRApp *)app {
self = [super init];
if (self) {
_app = app;
if (!_segmentationInstance) {
_segmentationInstance = [[FIRSegmentation alloc] initWithAppName:app.name
FIROptions:app.options];
}
}
return self;
}
#pragma mark - Lifecycle
+ (void)load {
// Register as an internal library to be part of the initialization process. The name comes from
// go/firebase-sdk-platform-info.
[FIRApp registerInternalLibrary:self
withName:@"fire-seg"
withVersion:[NSString stringWithUTF8String:STR(FIRSegmentation_VERSION)]];
}
#pragma mark - Interoperability
+ (NSArray<FIRComponent *> *)componentsToRegister {
FIRComponent *segProvider = [FIRComponent
componentWithProtocol:@protocol(FIRSegmentationProvider)
instantiationTiming:FIRInstantiationTimingAlwaysEager
dependencies:@[]
creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
// Cache the component so instances of Segmentation are cached.
*isCacheable = YES;
return [[FIRSegmentationComponent alloc] initWithApp:container.app];
}];
return @[ segProvider ];
}
@synthesize instances;
@end
@@ -0,0 +1,58 @@
/*
* Copyright 2019 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 "FIRLibrary.h"
@class FIRApp;
@class FIRSegmentation;
NS_ASSUME_NONNULL_BEGIN
/// Provides and creates instances of Segmentation. Used in the
/// interop registration process to keep track of Segmentation instances for each `FIRApp` instance.
@protocol FIRSegmentationProvider
/// Cached instances of Segmentation objects.
@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRSegmentation *> *instances;
/// Default method for retrieving a Segmentation instance, or creating one if it doesn't exist.
- (FIRSegmentation *)segmentation;
@end
/// A concrete implementation for FIRSegmentationInterop to create Segmentation instances and
/// register with Core's component system.
@interface FIRSegmentationComponent : NSObject <FIRSegmentationProvider, FIRLibrary>
/// The FIRApp that instances will be set up with.
@property(nonatomic, weak, readonly) FIRApp *app;
/// Cached instances of Segmentation objects.
@property(nonatomic, strong) FIRSegmentation *segmentationInstance;
/// Default method for retrieving a Segmentation instance, or creating one if it doesn't exist.
- (FIRSegmentation *)segmentation;
/// Default initializer.
- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
- (instancetype)init __attribute__((unavailable("Use `initWithApp:`.")));
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
/*
* Copyright 2019 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>
NS_ASSUME_NONNULL_BEGIN
@class FIRApp;
NS_SWIFT_NAME(Segmentation)
@interface FIRSegmentation : NSObject
/// Initialize a Segmentation instance with all the required parameters directly.
- (instancetype)initWithAppName:(NSString *)appName FIROptions:(FIROptions *)options;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,77 @@
/*
* Copyright 2019 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>
NS_ASSUME_NONNULL_BEGIN
@class FIRApp;
/**
* The Firebase Segmentation SDK is used to associate a custom, non-Firebase custom installation
* identifier to Firebase. Once this custom installation identifier is set, developers can use the
* current app installation for segmentation purposes. If the custom installation identifier is
* explicitely set to nil, any existing custom installation identifier data will be removed.
*/
NS_SWIFT_NAME(Segmentation)
@interface FIRSegmentation : NSObject
/// Firebase Segmentation service fetch error.
typedef NS_ENUM(NSInteger, FIRSegmentationErrorCode) {
/// No error. The operation was successful.
FIRSegmentationErrorCodeNone = 8001,
/// An internal error occurred.
FIRSegmentationErrorCodeInternal = 8002,
/// Error indicating that backend reports an existing association for this custom installation
/// identifier.
FIRSegmentationErrorCodeConflict = 8003,
/// Error indicating that a network error occurred during association.
FIRSegmentationErrorCodeNetwork = 8004,
} NS_SWIFT_NAME(SegmentationErrorCode);
/**
* Singleton instance (scoped to the default FIRApp)
* Returns the FIRSegmentation instance for the default Firebase application. Please make sure you
* call [FIRApp configure] beforehand for a default Firebase app to already be initialized and
* available. This singleton class instance lets you set your own custom identifier to be used for
* user segmentation purposes within Firebase.
*
* @return A shared instance of FIRSegmentation.
*/
+ (instancetype)segmentation;
/// Singleton instance (scoped to FIRApp)
/// Returns the FIRSegmentation instance for your Firebase application. This singleton class
/// instance lets you set your own custom identifier to be used for targeting purposes within
/// Firebase.
+ (instancetype)segmentationWithApp:(nonnull FIRApp *)app;
/**
* :nodoc:
* Unavailable. Use +segmentation instead.
*/
- (instancetype)init __attribute__((unavailable("Use +segmentation instead.")));
/// Set your own custom installation ID to be used for segmentation purposes.
/// This method needs to be called every time (and immediately) upon any changes to the custom
/// installation ID.
/// @param completionHandler Set custom installation ID completion. Returns nil if initialization
/// succeeded or an NSError object if initialization failed.
- (void)setCustomInstallationID:(nullable NSString *)customInstallationID
completion:(nullable void (^)(NSError *))completionHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,17 @@
/*
* Copyright 2019 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 "FIRSegmentation.h"
@@ -0,0 +1,34 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/SEGSegmentationConstants.h"
NS_ASSUME_NONNULL_BEGIN
@class FIROptions;
@interface SEGContentManager : NSObject
/// Shared Singleton Instance
+ (instancetype)sharedInstanceWithOptions:(FIROptions*)options;
- (void)associateCustomInstallationIdentiferNamed:(NSString*)customInstallationID
firebaseApp:(NSString*)appName
completion:(SEGRequestCompletion)completionHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,172 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/SEGContentManager.h"
#import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FirebaseInstallations.h"
#import "FirebaseSegmentation/Sources/Public/FIRSegmentation.h"
#import "FirebaseSegmentation/Sources/SEGDatabaseManager.h"
#import "FirebaseSegmentation/Sources/SEGNetworkManager.h"
#import "FirebaseSegmentation/Sources/SEGSegmentationConstants.h"
NSString *const kErrorDescription = @"ErrorDescription";
@interface SEGContentManager () {
NSMutableDictionary<NSString *, id> *_associationData;
NSString *_installationIdentifier;
NSString *_installationIdentifierToken;
SEGDatabaseManager *_databaseManager;
SEGNetworkManager *_networkManager;
}
@end
@implementation SEGContentManager
+ (instancetype)sharedInstanceWithOptions:(FIROptions *)options {
static dispatch_once_t onceToken;
static SEGContentManager *sharedInstance;
dispatch_once(&onceToken, ^{
sharedInstance = [[SEGContentManager alloc]
initWithDatabaseManager:[SEGDatabaseManager sharedInstance]
networkManager:[[SEGNetworkManager alloc] initWithOptions:options]];
});
return sharedInstance;
}
- (instancetype)initWithDatabaseManager:databaseManager networkManager:networkManager {
self = [super init];
if (self) {
// Initialize the database manager.
_databaseManager = databaseManager;
// Initialize the network manager.
_networkManager = networkManager;
// Load all data from the database.
[_databaseManager createOrOpenDatabaseWithCompletion:^(BOOL success, NSDictionary *result) {
self->_associationData = [result mutableCopy];
}];
// TODO(dmandar) subscribe to FIS notifications once integrated.
}
return self;
}
- (FIRInstallations *)installationForApp:(NSString *)firebaseApp {
return [FIRInstallations installationsWithApp:[FIRApp appNamed:firebaseApp]];
}
- (void)associateCustomInstallationIdentiferNamed:(NSString *)customInstallationID
firebaseApp:(NSString *)firebaseApp
completion:(SEGRequestCompletion)completionHandler {
// Get the latest installation identifier
FIRInstallations *installation = [self installationForApp:firebaseApp];
if (installation == nil) {
completionHandler(NO, @{kErrorDescription : @"Firebase Installations SDK not available"});
}
__weak SEGContentManager *weakSelf = self;
[installation
installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) {
SEGContentManager *strongSelf = weakSelf;
if (!strongSelf) {
completionHandler(NO, @{kErrorDescription : @"Internal Error getting installation ID."});
return;
}
[strongSelf associateInstallationWithLatestIdentifier:identifier
installation:installation
customizedIdentifier:customInstallationID
firebaseApp:firebaseApp
error:error
completion:completionHandler];
}];
}
- (void)associateInstallationWithLatestIdentifier:(NSString *_Nullable)identifier
installation:(FIRInstallations *)installation
customizedIdentifier:(NSString *)customInstallationID
firebaseApp:(NSString *)firebaseApp
error:(NSError *_Nullable)error
completion:(SEGRequestCompletion)completionHandler {
if (!identifier || error) {
NSString *errorMessage = @"Error getting installation ID.";
if (error) {
errorMessage = [errorMessage stringByAppendingString:error.description];
}
NSDictionary *errorDictionary = @{kErrorDescription : errorMessage};
completionHandler(NO, errorDictionary);
return;
}
_installationIdentifier = identifier;
__weak SEGContentManager *weakSelf = self;
[installation authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
NSError *_Nullable error) {
SEGContentManager *strongSelf = weakSelf;
if (!strongSelf) {
completionHandler(NO, @{kErrorDescription : @"Internal Error getting installation token."});
return;
}
[strongSelf associateInstallationWithToken:tokenResult
customizedIdentifier:customInstallationID
firebaseApp:firebaseApp
error:error
completion:completionHandler];
}];
}
- (void)associateInstallationWithToken:(FIRInstallationsAuthTokenResult *_Nullable)tokenResult
customizedIdentifier:(NSString *)customInstallationID
firebaseApp:(NSString *)firebaseApp
error:(NSError *_Nullable)error
completion:(SEGRequestCompletion)completionHandler {
if (!tokenResult || error) {
NSString *errorMessage = @"Error getting AuthToken.";
if (error) {
errorMessage = [errorMessage stringByAppendingString:error.description];
}
NSDictionary *errorDictionary = @{kErrorDescription : errorMessage};
completionHandler(NO, errorDictionary);
return;
}
_installationIdentifierToken = tokenResult.authToken;
NSMutableDictionary<NSString *, NSString *> *appAssociationData =
[[NSMutableDictionary alloc] init];
appAssociationData[kSEGCustomInstallationIdentifierKey] = customInstallationID;
appAssociationData[kSEGFirebaseInstallationIdentifierKey] = _installationIdentifier;
appAssociationData[kSEGAssociationStatusKey] = kSEGAssociationStatusPending;
_associationData[firebaseApp] = appAssociationData;
// Update the database async.
// TODO(mandard) The database write and corresponding completion handler needs to be wired up
// once we support listening to FID changes.
[_databaseManager insertMainTableApplicationNamed:firebaseApp
customInstanceIdentifier:customInstallationID
firebaseInstanceIdentifier:_installationIdentifier
associationStatus:kSEGAssociationStatusPending
completionHandler:nil];
// Send the change up to the backend. Also add the token.
[_networkManager
makeAssociationRequestToBackendWithData:appAssociationData
token:_installationIdentifierToken
completion:^(BOOL status, NSDictionary<NSString *, id> *result) {
// TODO: log, update database.
completionHandler(status, result);
}];
}
@end
@@ -0,0 +1,54 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/SEGSegmentationConstants.h"
NS_ASSUME_NONNULL_BEGIN
/// Persist config data in sqlite database on device. Managing data read/write from/to database.
@interface SEGDatabaseManager : NSObject
/// Shared Singleton Instance
+ (instancetype)sharedInstance;
/// Open the database.
- (void)createOrOpenDatabaseWithCompletion:(SEGRequestCompletion)completionHandler;
/// Read all contents of main table.
- (void)loadMainTableWithCompletion:(SEGRequestCompletion)completionHandler;
/// Insert a record in main table.
/// @param firebaseApplication The name of the Firebase App that this segmentation instance is
/// associated with.
/// @param customInstanceIdentifier The custom instance identifier provided by the developer.
/// @param firebaseInstanceIdentifier The firebase instance identifier provided by the IID/FIS SDK.
/// @param associationStatus The current status of the association - Pending until reported to the
/// backend.
- (void)insertMainTableApplicationNamed:(NSString *)firebaseApplication
customInstanceIdentifier:(NSString *)customInstanceIdentifier
firebaseInstanceIdentifier:(NSString *)firebaseInstanceIdentifier
associationStatus:(NSString *)associationStatus
completionHandler:(nullable SEGRequestCompletion)handler;
/// Clear the record of given namespace and package name
/// before updating the table.//TODO: Add delete.
- (void)deleteRecordFromMainTableWithCustomInstanceIdentifier:(NSString *)customInstanceIdentifier;
/// Remove all the records from a config content table.
- (void)deleteAllRecordsFromTable;
NS_ASSUME_NONNULL_END
@end
@@ -0,0 +1,417 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/SEGDatabaseManager.h"
#import <sqlite3.h>
#import "FirebaseCore/Sources/Private/FIRLogger.h"
/// SQLite file name.
static NSString *const kDatabaseName = @"FirebaseSegmentation.sqlite3";
/// The application support sub-directory that the Segmentation database resides in.
static NSString *const kApplicationSupportSubDirectory = @"Google/FirebaseSegmentation";
/// Column names
static NSString *const kMainTableName = @"main";
static NSString *const kMainTableColumnApplicationIdentifier = @"firebase_app_identifier";
static NSString *const kMainTableColumnCustomInstallationIdentifier =
@"custom_installation_identifier";
static NSString *const kMainTableColumnFirebaseInstallationIdentifier =
@"firebase_installation_identifier";
static NSString *const kMainTableColumnAssociationStatus = @"association_status";
// Exclude the database from iCloud backup.
static BOOL SEGAddSkipBackupAttributeToItemAtPath(NSString *filePathString) {
NSURL *URL = [NSURL fileURLWithPath:filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath:URL.path]);
NSError *error = nil;
BOOL success = [URL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error];
if (!success) {
// TODO(dmandar): log error.
NSLog(@"Error excluding %@ from backup %@.", [URL lastPathComponent], error);
}
return success;
}
static BOOL SEGCreateFilePathIfNotExist(NSString *filePath) {
if (!filePath || !filePath.length) {
// TODO(dmandar) log error.
NSLog(@"Failed to create subdirectory for an empty file path.");
return NO;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:filePath]) {
NSError *error;
[fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
// TODO(dmandar) log error.
NSLog(@"Failed to create subdirectory for database file: %@.", error);
return NO;
}
}
return YES;
}
@interface SEGDatabaseManager () {
/// Database storing all the config information.
sqlite3 *_database;
/// Serial queue for database read/write operations.
dispatch_queue_t _databaseOperationQueue;
}
@end
@implementation SEGDatabaseManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static SEGDatabaseManager *sharedInstance;
dispatch_once(&onceToken, ^{
sharedInstance = [[SEGDatabaseManager alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_databaseOperationQueue =
dispatch_queue_create("com.google.firebasesegmentation.database", DISPATCH_QUEUE_SERIAL);
}
return self;
}
#pragma mark - Public Methods
- (void)loadMainTableWithCompletion:(SEGRequestCompletion)completionHandler {
__weak SEGDatabaseManager *weakSelf = self;
dispatch_async(_databaseOperationQueue, ^{
SEGDatabaseManager *strongSelf = weakSelf;
if (!strongSelf) {
completionHandler(NO, @{@"Database Error" : @"Internal database error"});
}
// Read the database into memory.
NSDictionary<NSString *, NSDictionary<NSString *, NSString *> *> *associations =
[self loadMainTable];
completionHandler(YES, associations);
});
return;
}
- (void)createOrOpenDatabaseWithCompletion:(SEGRequestCompletion)completionHandler {
__weak SEGDatabaseManager *weakSelf = self;
dispatch_async(_databaseOperationQueue, ^{
SEGDatabaseManager *strongSelf = weakSelf;
if (!strongSelf) {
completionHandler(NO, @{@"ErrorDescription" : @"Internal database error"});
}
NSString *dbPath = [SEGDatabaseManager pathForSegmentationDatabase];
// TODO(dmandar) log.
NSLog(@"Loading segmentation database at path %@", dbPath);
const char *databasePath = dbPath.UTF8String;
// Create or open database path.
if (!SEGCreateFilePathIfNotExist(dbPath)) {
completionHandler(NO, @{@"ErrorDescription" : @"Could not create database file at path"});
}
int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FILEPROTECTION_COMPLETE |
SQLITE_OPEN_FULLMUTEX;
if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
// Create table if does not exist already.
if ([strongSelf createTableSchema]) {
// DB file created or already exists.
// Exclude the app data used from iCloud backup.
SEGAddSkipBackupAttributeToItemAtPath(dbPath);
// Read the database into memory.
NSDictionary<NSString *, NSString *> *associations = [self loadMainTable];
completionHandler(YES, associations);
} else {
// Remove database before fail.
[strongSelf removeDatabase:dbPath];
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000010", @"Failed to create table.");
// Create a new database if existing database file is corrupted.
if (!SEGCreateFilePathIfNotExist(dbPath)) {
completionHandler(NO,
@{@"ErrorDescription" : @"Could not recreate database file at path"});
}
if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
if (![strongSelf createTableSchema]) {
// Remove database before fail.
[strongSelf removeDatabase:dbPath];
// If it failed again, there's nothing we can do here.
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000010", @"Failed to create table.");
} else {
// Exclude the app data used from iCloud backup.
SEGAddSkipBackupAttributeToItemAtPath(dbPath);
}
} else {
[strongSelf logDatabaseError];
completionHandler(NO, @{@"ErrorDescription" : @"Could not create database."});
}
}
} else {
[strongSelf logDatabaseError];
completionHandler(NO, @{@"ErrorDescription" : @"Error creating database."});
}
});
}
- (void)removeDatabase:(NSString *)path completion:(SEGRequestCompletion)completionHandler {
dispatch_async(_databaseOperationQueue, ^{
SEGDatabaseManager *strongSelf = self;
if (!strongSelf) {
return;
}
[strongSelf removeDatabase:path];
completionHandler(YES, nil);
});
}
#pragma mark - Private Methods
- (NSDictionary *)loadMainTable {
NSString *SQLQuery = [NSString
stringWithFormat:@"SELECT %@, %@, %@, %@ FROM %@", kMainTableColumnApplicationIdentifier,
kMainTableColumnCustomInstallationIdentifier,
kMainTableColumnFirebaseInstallationIdentifier,
kMainTableColumnAssociationStatus, kMainTableName];
sqlite3_stmt *statement = [self prepareSQL:[SQLQuery cStringUsingEncoding:NSUTF8StringEncoding]];
if (!statement) {
return nil;
}
NSMutableDictionary<NSString *, NSDictionary<NSString *, NSString *> *> *associations =
[[NSMutableDictionary alloc] init];
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *firebaseApplicationName =
[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
NSString *customInstallationIdentifier =
[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSString *firebaseInstallationIdentifier =
[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
NSString *associationStatus =
[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 3)];
NSDictionary<NSString *, NSString *> *associationData = @{
kSEGCustomInstallationIdentifierKey : customInstallationIdentifier,
kSEGFirebaseInstallationIdentifierKey : firebaseInstallationIdentifier,
kSEGAssociationStatusKey : associationStatus
};
[associations setObject:associationData forKey:firebaseApplicationName];
}
sqlite3_finalize(statement);
return associations;
}
/// Returns the current version of the Remote Config database.
+ (NSString *)pathForSegmentationDatabase {
NSArray<NSString *> *dirPaths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportPath = dirPaths.firstObject;
NSArray<NSString *> *components =
@[ appSupportPath, kApplicationSupportSubDirectory, kDatabaseName ];
return [NSString pathWithComponents:components];
}
- (BOOL)createTableSchema {
SEG_MUST_NOT_BE_MAIN_THREAD();
NSString *mainTableSchema =
[NSString stringWithFormat:@"create TABLE IF NOT EXISTS %@ (_id INTEGER PRIMARY KEY, %@ "
@"TEXT, %@ TEXT, %@ TEXT, %@ TEXT)",
kMainTableName, kMainTableColumnApplicationIdentifier,
kMainTableColumnCustomInstallationIdentifier,
kMainTableColumnFirebaseInstallationIdentifier,
kMainTableColumnAssociationStatus];
return [self executeQuery:[mainTableSchema cStringUsingEncoding:NSUTF8StringEncoding]];
}
- (void)removeDatabase:(NSString *)path {
SEG_MUST_NOT_BE_MAIN_THREAD();
if (sqlite3_close(self->_database) != SQLITE_OK) {
[self logDatabaseError];
}
self->_database = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
if (![fileManager removeItemAtPath:path error:&error]) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000011",
@"Failed to remove database at path %@ for error %@.", path, error);
}
}
#pragma mark - execute
- (BOOL)executeQuery:(const char *)SQL {
SEG_MUST_NOT_BE_MAIN_THREAD();
char *error;
if (sqlite3_exec(_database, SQL, nil, nil, &error) != SQLITE_OK) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000012", @"Failed to execute query with error %s.",
error);
return NO;
}
return YES;
}
#pragma mark - insert
- (void)insertMainTableApplicationNamed:(NSString *)firebaseApplication
customInstanceIdentifier:(NSString *)customInstanceIdentifier
firebaseInstanceIdentifier:(NSString *)firebaseInstanceIdentifier
associationStatus:(NSString *)associationStatus
completionHandler:(SEGRequestCompletion)handler {
// TODO: delete the row first.
__weak SEGDatabaseManager *weakSelf = self;
dispatch_async(_databaseOperationQueue, ^{
NSArray<NSString *> *values =
[[NSArray alloc] initWithObjects:firebaseApplication, customInstanceIdentifier,
firebaseInstanceIdentifier, associationStatus, nil];
BOOL success = [weakSelf insertMainTableWithValues:values];
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(success, nil);
});
}
});
}
- (BOOL)insertMainTableWithValues:(NSArray<NSString *> *)values {
SEG_MUST_NOT_BE_MAIN_THREAD();
if (values.count != 4) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000013",
@"Failed to insert config record. Wrong number of give parameters, current "
@"number is %ld, correct number is 4.",
(long)values.count);
return NO;
}
NSString *SQL = [NSString stringWithFormat:@"INSERT INTO %@ (%@, %@, %@, %@) values (?, ?, ?, ?)",
kMainTableName, kMainTableColumnApplicationIdentifier,
kMainTableColumnCustomInstallationIdentifier,
kMainTableColumnFirebaseInstallationIdentifier,
kMainTableColumnAssociationStatus];
sqlite3_stmt *statement = [self prepareSQL:[SQL UTF8String]];
if (!statement) {
return NO;
}
NSString *aString = values[0];
if (![self bindStringToStatement:statement index:1 string:aString]) {
return [self logErrorWithSQL:[SQL UTF8String] finalizeStatement:statement returnValue:NO];
}
aString = values[1];
if (![self bindStringToStatement:statement index:2 string:aString]) {
return [self logErrorWithSQL:[SQL UTF8String] finalizeStatement:statement returnValue:NO];
}
aString = values[2];
if (![self bindStringToStatement:statement index:3 string:aString]) {
return [self logErrorWithSQL:[SQL UTF8String] finalizeStatement:statement returnValue:NO];
}
aString = values[3];
if (![self bindStringToStatement:statement index:4 string:aString]) {
return [self logErrorWithSQL:[SQL UTF8String] finalizeStatement:statement returnValue:NO];
}
if (sqlite3_step(statement) != SQLITE_DONE) {
return [self logErrorWithSQL:[SQL UTF8String] finalizeStatement:statement returnValue:NO];
}
sqlite3_finalize(statement);
return YES;
}
/// TODO: (Check if required). Clear the record of given namespace and package name
/// before updating the table.
- (void)deleteRecordFromMainTableWithCustomInstanceIdentifier:
(nonnull NSString *)customInstanceIdentifier {
}
/// TODO: (Check if required). Remove all the records from a config content table.
- (void)deleteAllRecordsFromTable {
}
#pragma mark - helper
- (BOOL)executeQuery:(const char *)SQL withParams:(NSArray *)params {
SEG_MUST_NOT_BE_MAIN_THREAD();
sqlite3_stmt *statement = [self prepareSQL:SQL];
if (!statement) {
return NO;
}
[self bindStringsToStatement:statement stringArray:params];
if (sqlite3_step(statement) != SQLITE_DONE) {
return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
}
sqlite3_finalize(statement);
return YES;
}
/// Params only accept TEXT format string.
- (BOOL)bindStringsToStatement:(sqlite3_stmt *)statement stringArray:(NSArray *)array {
int index = 1;
for (NSString *param in array) {
if (![self bindStringToStatement:statement index:index string:param]) {
return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
}
index++;
}
return YES;
}
- (BOOL)bindStringToStatement:(sqlite3_stmt *)statement index:(int)index string:(NSString *)value {
if (sqlite3_bind_text(statement, index, [value UTF8String], -1, SQLITE_TRANSIENT) != SQLITE_OK) {
return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
}
return YES;
}
- (sqlite3_stmt *)prepareSQL:(const char *)SQL {
sqlite3_stmt *statement = nil;
if (sqlite3_prepare_v2(_database, SQL, -1, &statement, NULL) != SQLITE_OK) {
[self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
return nil;
}
return statement;
}
- (NSString *)errorMessage {
return [NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)];
}
- (int)errorCode {
return sqlite3_errcode(_database);
}
- (void)logDatabaseError {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000015", @"Error message: %@. Error code: %d.",
[self errorMessage], [self errorCode]);
}
- (BOOL)logErrorWithSQL:(const char *)SQL
finalizeStatement:(sqlite3_stmt *)statement
returnValue:(BOOL)returnValue {
if (SQL) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000016", @"Failed with SQL: %s.", SQL);
}
[self logDatabaseError];
if (statement) {
sqlite3_finalize(statement);
}
return returnValue;
}
@end
@@ -0,0 +1,34 @@
// Copyright 2019 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 "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import "FirebaseSegmentation/Sources/SEGSegmentationConstants.h"
NS_ASSUME_NONNULL_BEGIN
@interface SEGNetworkManager : NSObject
- (instancetype)initWithOptions:(FIROptions *)options;
- (void)makeAssociationRequestToBackendWithData:
(nonnull NSDictionary<NSString *, id> *)associationData
token:(NSString *)token
completion:(SEGRequestCompletion)completionHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,223 @@
// Copyright 2019 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 "FirebaseSegmentation/Sources/SEGNetworkManager.h"
#import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
// TODO(dmandar): define in build file.
#define SEG_ALPHA_SERVER
static NSString *const kServerURLDomain = @"https://firebasesegmentation.googleapis.com";
#ifdef SEG_ALPHA_SERVER
static NSString *const kServerURLVersion = @"/v1alpha";
#else
static NSString *const kServerURLVersion = @"/v1";
#endif
static NSString *const kServerURLStringProjects = @"/projects/";
static NSString *const kServerURLStringInstallations = @"/installations/";
static NSString *const kServerURLStringCustomSegmentationData = @"/customSegmentationData";
static NSString *const kHTTPMethodPatch = @"PATCH";
static NSString *const kRequestHeaderAuthorizationValueString = @"FIREBASE_INSTALLATIONS_AUTH";
static NSString *const kRequestDataCustomInstallationIdString = @"custom_installation_id";
// HTTP header names.
static NSString *const kHeaderNameAPIKey = @"x-goog-api-key";
static NSString *const kHeaderNameFirebaseAuthorizationToken = @"Authorization";
static NSString *const kHeaderNameContentType = @"Content-Type";
static NSString *const kHeaderNameContentEncoding = @"Content-Encoding";
static NSString *const kHeaderNameAcceptEncoding = @"Accept-Encoding";
// Sends the bundle ID. Refer to b/130301479 for details.
static NSString *const kiOSBundleIdentifierHeaderName =
@"X-Ios-Bundle-Identifier"; ///< HTTP Header Field Name
/// Config HTTP request content type JSON
static NSString *const kContentTypeValueJSON = @"application/json";
// TODO: Handle error codes.
/// HTTP status codes. Ref: https://cloud.google.com/apis/design/errors#error_retries
static NSInteger const kSEGResponseHTTPStatusCodeOK = 200;
// static NSInteger const kSEGResponseHTTPStatusCodeConflict = 409;
// static NSInteger const kSEGResponseHTTPStatusTooManyRequests = 429;
// static NSInteger const kSEGResponseHTTPStatusCodeInternalError = 500;
// static NSInteger const kSEGResponseHTTPStatusCodeServiceUnavailable = 503;
// static NSInteger const kSEGResponseHTTPStatusCodeGatewayTimeout = 504;
// HTTP default timeout.
static NSTimeInterval const kSEGHTTPRequestTimeout = 60;
/// Completion handler invoked by URLSession completion handler.
typedef void (^URLSessionCompletion)(NSData *data, NSURLResponse *response, NSError *error);
@implementation SEGNetworkManager {
FIROptions *_firebaseAppOptions;
NSURLSession *_URLSession;
}
- (instancetype)initWithOptions:(FIROptions *)options {
self = [super init];
if (self) {
_firebaseAppOptions = options;
_URLSession = [self newURLSession];
}
return self;
}
- (void)dealloc {
[_URLSession invalidateAndCancel];
}
- (void)makeAssociationRequestToBackendWithData:
(NSDictionary<NSString *, NSString *> *)associationData
token:(NSString *)token
completion:(SEGRequestCompletion)completionHandler {
// Construct the server URL.
NSString *URL = [self constructServerURLWithAssociationData:associationData];
if (!URL) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000020", @"Could not construct backend URL.");
completionHandler(NO, @{@"errorDescription" : @"Could not construct backend URL"});
}
FIRLogDebug(kFIRLoggerSegmentation, @"I-SEG000019", @"%@",
[NSString stringWithFormat:@"Making config request: %@", URL]);
// Construct the request data.
NSString *customInstallationIdentifier =
[associationData objectForKey:kSEGCustomInstallationIdentifierKey];
// TODO: Add tests for nil.
NSDictionary<NSString *, NSString *> *requestDataDictionary =
@{kRequestDataCustomInstallationIdString : customInstallationIdentifier};
NSError *error = nil;
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestDataDictionary
options:0
error:nil];
if (!requestData || error) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000021", @"Could not create request data. %@",
error.localizedDescription);
completionHandler(NO,
@{@"errorDescription" : @"Could not serialize JSON data for network call."});
}
// Handle NSURLSession completion.
__weak SEGNetworkManager *weakSelf = self;
[self URLSessionDataTaskWithURL:URL
content:requestData
token:token
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
SEGNetworkManager *strongSelf = weakSelf;
if (!strongSelf) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000022",
@"Internal error making network request.");
completionHandler(
NO, @{@"errorDescription" : @"Internal error making network request."});
return;
}
NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
if (!error && (statusCode == kSEGResponseHTTPStatusCodeOK)) {
FIRLogDebug(kFIRLoggerSegmentation, @"I-SEG000017",
@"SEGNetworkManager: Network request successful.");
completionHandler(YES, nil);
} else {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000018",
@"SEGNetworkManager: Network request failed with status code:%lu",
(long)statusCode);
completionHandler(NO, @{
@"ErrorDescription" :
[NSString stringWithFormat:@"Network Error: %lu", (long)statusCode]
});
};
}];
}
#pragma mark Private
- (NSURLSession *)newURLSession {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.timeoutIntervalForRequest = kSEGHTTPRequestTimeout;
config.timeoutIntervalForResource = kSEGHTTPRequestTimeout;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
return session;
}
- (NSString *)constructServerURLWithAssociationData:
(NSDictionary<NSString *, NSString *> *)associationData {
NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
serverURLStr = [serverURLStr stringByAppendingString:kServerURLStringProjects];
if (_firebaseAppOptions.projectID) {
serverURLStr = [serverURLStr stringByAppendingString:_firebaseAppOptions.projectID];
} else {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000070",
@"Missing `projectID` from `FirebaseOptions`, please ensure the configured "
@"`FirebaseApp` is configured with `FirebaseOptions` that contains a `projectID`.");
return nil;
}
serverURLStr = [serverURLStr stringByAppendingString:kServerURLStringInstallations];
// Get the FID.
NSString *firebaseInstallationIdentifier =
[associationData objectForKey:kSEGFirebaseInstallationIdentifierKey];
if (!firebaseInstallationIdentifier) {
FIRLogError(kFIRLoggerSegmentation, @"I-SEG000071",
@"Missing Firebase installation identifier");
return nil;
}
serverURLStr = [serverURLStr stringByAppendingString:firebaseInstallationIdentifier];
serverURLStr = [serverURLStr stringByAppendingString:kServerURLStringCustomSegmentationData];
return serverURLStr;
}
- (void)URLSessionDataTaskWithURL:(NSString *)stringURL
content:(NSData *)content
token:(NSString *)token
completionHandler:(URLSessionCompletion)completionHandler {
NSTimeInterval timeoutInterval = kSEGHTTPRequestTimeout;
NSURL *URL = [NSURL URLWithString:stringURL];
NSMutableURLRequest *URLRequest =
[[NSMutableURLRequest alloc] initWithURL:URL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:timeoutInterval];
URLRequest.HTTPMethod = kHTTPMethodPatch;
// Setup headers.
[URLRequest setValue:_firebaseAppOptions.APIKey forHTTPHeaderField:kHeaderNameAPIKey];
NSString *authorizationTokenHeaderValue =
[NSString stringWithFormat:@"%@ %@", kRequestHeaderAuthorizationValueString, token];
[URLRequest setValue:authorizationTokenHeaderValue
forHTTPHeaderField:kHeaderNameFirebaseAuthorizationToken];
// TODO: Check if we accept gzip.
// [URLRequest setValue:@"gzip" forHTTPHeaderField:kHeaderNameContentEncoding];
// [URLRequest setValue:@"gzip" forHTTPHeaderField:kHeaderNameAcceptEncoding];
// Send the bundleID for API Key restrictions.
[URLRequest setValue:[[NSBundle mainBundle] bundleIdentifier]
forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
[URLRequest setHTTPBody:content];
NSURLSessionDataTask *task = [_URLSession dataTaskWithRequest:URLRequest
completionHandler:completionHandler];
[task resume];
}
@end
@@ -0,0 +1,52 @@
/*
* Copyright 2019 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>
#ifndef SEGSegmentationConstants_h
#define SEGSegmentationConstants_h
#if defined(DEBUG)
#define SEG_MUST_NOT_BE_MAIN_THREAD() \
do { \
NSAssert(![NSThread isMainThread], @"Must not be executing on the main thread."); \
} while (0);
#else
#define SEG_MUST_NOT_BE_MAIN_THREAD() \
do { \
} while (0);
#endif
static NSString* const kFIRLoggerSegmentation = @"[Firebase/Segmentation]";
/// Keys for values stored in the Segmentation SDK.
static NSString* const kSEGFirebaseApplicationIdentifierKey = @"firebase_app_identifier";
static NSString* const kSEGCustomInstallationIdentifierKey = @"custom_installation_identifier";
static NSString* const kSEGFirebaseInstallationIdentifierKey = @"firebase_installation_identifier";
static NSString* const kSEGAssociationStatusKey = @"association_status";
/// Association Status
static NSString* const kSEGAssociationStatusPending = @"PENDING";
static NSString* const kSEGAssociationStatusAssociated = @"ASSOCIATED";
/// Segmentation error domain when logging errors.
static NSString* const kFirebaseSegmentationErrorDomain = @"com.firebase.segmentation";
/// Segmentation Request Completion callback.
/// @param success Decide whether the network operation succeeds.
/// @param result Return operation result data.
typedef void (^SEGRequestCompletion)(BOOL success, NSDictionary<NSString*, id>* result);
#endif /* SEGSegmentationConstants_h */
+15
View File
@@ -0,0 +1,15 @@
# Uncomment the next two lines for pre-release testing on internal repo
#source 'sso://cpdc-internal/firebase'
#source 'https://cdn.cocoapods.org/'
target 'SegmentationSampleApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
inherit! :search_paths
platform :ios, '8.0'
# Pods for SegmentationSampleApp
pod 'FirebaseCore', :path => '../../../'
pod 'FirebaseSegmentation', :path => '../../../'
end
@@ -0,0 +1,426 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
5B4DE0A622F8D7B100B55A7B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0A522F8D7B100B55A7B /* AppDelegate.m */; };
5B4DE0A922F8D7B100B55A7B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0A822F8D7B100B55A7B /* ViewController.m */; };
5B4DE0AC22F8D7B100B55A7B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B4DE0AA22F8D7B100B55A7B /* Main.storyboard */; };
5B4DE0AE22F8D7B300B55A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5B4DE0AD22F8D7B300B55A7B /* Assets.xcassets */; };
5B4DE0B122F8D7B300B55A7B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B4DE0AF22F8D7B300B55A7B /* LaunchScreen.storyboard */; };
5B4DE0B422F8D7B300B55A7B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0B322F8D7B300B55A7B /* main.m */; };
5B4DE0BD22F8DA1300B55A7B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B4DE0BB22F8DA1300B55A7B /* GoogleService-Info.plist */; };
AC252CE6B594C95D235E64F9 /* Pods_SegmentationSampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03FB7E52AF4C6B5EAA580F43 /* Pods_SegmentationSampleApp.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
03FB7E52AF4C6B5EAA580F43 /* Pods_SegmentationSampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SegmentationSampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
23212B47B363359777FCF914 /* Pods-SegmentationSampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SegmentationSampleApp.debug.xcconfig"; path = "Target Support Files/Pods-SegmentationSampleApp/Pods-SegmentationSampleApp.debug.xcconfig"; sourceTree = "<group>"; };
2B8D0D05EDF54422ACEF8C82 /* Pods-SegmentationSampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SegmentationSampleApp.release.xcconfig"; path = "Target Support Files/Pods-SegmentationSampleApp/Pods-SegmentationSampleApp.release.xcconfig"; sourceTree = "<group>"; };
5B4DE0A122F8D7B100B55A7B /* SegmentationSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SegmentationSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
5B4DE0A422F8D7B100B55A7B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
5B4DE0A522F8D7B100B55A7B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
5B4DE0A722F8D7B100B55A7B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
5B4DE0A822F8D7B100B55A7B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
5B4DE0AB22F8D7B100B55A7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
5B4DE0AD22F8D7B300B55A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5B4DE0B022F8D7B300B55A7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
5B4DE0B222F8D7B300B55A7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5B4DE0B322F8D7B300B55A7B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
5B4DE0BA22F8DA1200B55A7B /* SecondApp-GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SecondApp-GoogleService-Info.plist"; sourceTree = "<group>"; };
5B4DE0BB22F8DA1300B55A7B /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
5B4DE0BF22F8E07200B55A7B /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../../configdaily googservice/GoogleService-Info.plist"; sourceTree = "<group>"; };
5B4DE0C222F8E09900B55A7B /* SecondApp-GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SecondApp-GoogleService-Info.plist"; sourceTree = "<group>"; };
5B4DE0C322F8E09900B55A7B /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
5B4DE09E22F8D7B100B55A7B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AC252CE6B594C95D235E64F9 /* Pods_SegmentationSampleApp.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1DAAA9CDE17A7AD3D4D58BFA /* Pods */ = {
isa = PBXGroup;
children = (
23212B47B363359777FCF914 /* Pods-SegmentationSampleApp.debug.xcconfig */,
2B8D0D05EDF54422ACEF8C82 /* Pods-SegmentationSampleApp.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
2074D9A873FD02A71321C678 /* Frameworks */ = {
isa = PBXGroup;
children = (
5B4DE0BF22F8E07200B55A7B /* GoogleService-Info.plist */,
03FB7E52AF4C6B5EAA580F43 /* Pods_SegmentationSampleApp.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
5B4DE09822F8D7B100B55A7B = {
isa = PBXGroup;
children = (
5B4DE0BB22F8DA1300B55A7B /* GoogleService-Info.plist */,
5B4DE0C322F8E09900B55A7B /* GoogleService-Info.plist */,
5B4DE0BA22F8DA1200B55A7B /* SecondApp-GoogleService-Info.plist */,
5B4DE0C222F8E09900B55A7B /* SecondApp-GoogleService-Info.plist */,
5B4DE0A322F8D7B100B55A7B /* SegmentationSampleApp */,
5B4DE0A222F8D7B100B55A7B /* Products */,
1DAAA9CDE17A7AD3D4D58BFA /* Pods */,
2074D9A873FD02A71321C678 /* Frameworks */,
);
sourceTree = "<group>";
};
5B4DE0A222F8D7B100B55A7B /* Products */ = {
isa = PBXGroup;
children = (
5B4DE0A122F8D7B100B55A7B /* SegmentationSampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
5B4DE0A322F8D7B100B55A7B /* SegmentationSampleApp */ = {
isa = PBXGroup;
children = (
5B4DE0A422F8D7B100B55A7B /* AppDelegate.h */,
5B4DE0A522F8D7B100B55A7B /* AppDelegate.m */,
5B4DE0A722F8D7B100B55A7B /* ViewController.h */,
5B4DE0A822F8D7B100B55A7B /* ViewController.m */,
5B4DE0AA22F8D7B100B55A7B /* Main.storyboard */,
5B4DE0AD22F8D7B300B55A7B /* Assets.xcassets */,
5B4DE0AF22F8D7B300B55A7B /* LaunchScreen.storyboard */,
5B4DE0B222F8D7B300B55A7B /* Info.plist */,
5B4DE0B322F8D7B300B55A7B /* main.m */,
);
path = SegmentationSampleApp;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
5B4DE0A022F8D7B100B55A7B /* SegmentationSampleApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5B4DE0B722F8D7B300B55A7B /* Build configuration list for PBXNativeTarget "SegmentationSampleApp" */;
buildPhases = (
B73B50EA390CE7F1ED248C8C /* [CP] Check Pods Manifest.lock */,
5B4DE09D22F8D7B100B55A7B /* Sources */,
5B4DE09E22F8D7B100B55A7B /* Frameworks */,
5B4DE09F22F8D7B100B55A7B /* Resources */,
F28D7493DD28A2A2A72AF1FC /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = SegmentationSampleApp;
productName = SegmentationSampleApp;
productReference = 5B4DE0A122F8D7B100B55A7B /* SegmentationSampleApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
5B4DE09922F8D7B100B55A7B /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1010;
ORGANIZATIONNAME = "Mandar Deolalikar";
TargetAttributes = {
5B4DE0A022F8D7B100B55A7B = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 5B4DE09C22F8D7B100B55A7B /* Build configuration list for PBXProject "SegmentationSampleApp" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 5B4DE09822F8D7B100B55A7B;
productRefGroup = 5B4DE0A222F8D7B100B55A7B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
5B4DE0A022F8D7B100B55A7B /* SegmentationSampleApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
5B4DE09F22F8D7B100B55A7B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B4DE0BD22F8DA1300B55A7B /* GoogleService-Info.plist in Resources */,
5B4DE0B122F8D7B300B55A7B /* LaunchScreen.storyboard in Resources */,
5B4DE0AE22F8D7B300B55A7B /* Assets.xcassets in Resources */,
5B4DE0AC22F8D7B100B55A7B /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
B73B50EA390CE7F1ED248C8C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SegmentationSampleApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
F28D7493DD28A2A2A72AF1FC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SegmentationSampleApp/Pods-SegmentationSampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SegmentationSampleApp/Pods-SegmentationSampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SegmentationSampleApp/Pods-SegmentationSampleApp-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
5B4DE09D22F8D7B100B55A7B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B4DE0A922F8D7B100B55A7B /* ViewController.m in Sources */,
5B4DE0B422F8D7B300B55A7B /* main.m in Sources */,
5B4DE0A622F8D7B100B55A7B /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
5B4DE0AA22F8D7B100B55A7B /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
5B4DE0AB22F8D7B100B55A7B /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
5B4DE0AF22F8D7B300B55A7B /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
5B4DE0B022F8D7B300B55A7B /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
5B4DE0B522F8D7B300B55A7B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
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;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
5B4DE0B622F8D7B300B55A7B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
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;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
5B4DE0B822F8D7B300B55A7B /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 23212B47B363359777FCF914 /* Pods-SegmentationSampleApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = EQHXZ8M8AV;
INFOPLIST_FILE = SegmentationSampleApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.config.testapp.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Firebase Remote Config TestApp Dev";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
5B4DE0B922F8D7B300B55A7B /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2B8D0D05EDF54422ACEF8C82 /* Pods-SegmentationSampleApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = EQHXZ8M8AV;
INFOPLIST_FILE = SegmentationSampleApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.config.testapp.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Firebase Remote Config TestApp Dev";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
5B4DE09C22F8D7B100B55A7B /* Build configuration list for PBXProject "SegmentationSampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5B4DE0B522F8D7B300B55A7B /* Debug */,
5B4DE0B622F8D7B300B55A7B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5B4DE0B722F8D7B300B55A7B /* Build configuration list for PBXNativeTarget "SegmentationSampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5B4DE0B822F8D7B300B55A7B /* Debug */,
5B4DE0B922F8D7B300B55A7B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 5B4DE09922F8D7B100B55A7B /* Project object */;
}
@@ -0,0 +1,21 @@
// Copyright 2019 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 <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property(strong, nonatomic) UIWindow *window;
@end
@@ -0,0 +1,39 @@
// Copyright 2019 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 "AppDelegate.h"
#import <FirebaseCore/FirebaseCore.h>
#import "FirebaseSegmentation.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[FIRApp configure];
FIRSegmentation *segmentation = [FIRSegmentation segmentation];
[segmentation setCustomInstallationID:@"mandard-test-custom-installation-id3"
completion:^(NSError *error) {
if (error) {
NSLog(@"Error! Could not set custom id");
}
}];
return YES;
}
@end
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
</dict>
</plist>
@@ -0,0 +1,19 @@
// Copyright 2019 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 <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
@@ -0,0 +1,28 @@
// Copyright 2019 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 "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
@end
@@ -0,0 +1,22 @@
// Copyright 2019 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 <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@@ -0,0 +1,122 @@
// Copyright 2019 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 <XCTest/XCTest.h>
#import "FirebaseSegmentation/Sources/SEGContentManager.h"
#import "FirebaseSegmentation/Sources/SEGDatabaseManager.h"
#import "FirebaseSegmentation/Sources/SEGNetworkManager.h"
#import <OCMock/OCMock.h>
#import "FirebaseCore/Sources/Public/FirebaseCore/FirebaseCore.h"
#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h"
#import "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FirebaseInstallations.h"
@interface SEGContentManager (ForTest)
- (instancetype)initWithDatabaseManager:databaseManager networkManager:networkManager;
@end
@interface FIRInstallations (Tests)
@property(nonatomic, readwrite, strong) FIROptions *appOptions;
@property(nonatomic, readwrite, strong) NSString *appName;
- (instancetype)initWithAppOptions:(FIROptions *)appOptions
appName:(NSString *)appName
installationsIDController:(FIRInstallationsIDController *)installationsIDController
prefetchAuthToken:(BOOL)prefetchAuthToken;
@end
@interface FIRInstallationsAuthTokenResult (ForTest)
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationTime;
@end
@interface SEGContentManagerTests : XCTestCase
@property(nonatomic) SEGContentManager *contentManager;
@property(nonatomic) id networkManagerMock;
@property(nonatomic) id mockIDController;
@property(nonatomic) FIROptions *appOptions;
@property(readonly) NSString *firebaseAppName;
@property(strong, readonly, nonatomic) id mockInstallations;
@end
@implementation SEGContentManagerTests
- (void)setUp {
// Setup FIRApp.
_firebaseAppName = @"my-firebase-app-id";
XCTAssertNoThrow([FIRApp configureWithName:self.firebaseAppName options:[self FIRAppOptions]]);
// Installations Mock
NSString *FID = @"fid-is-better-than-iid";
_mockInstallations = OCMClassMock([FIRInstallations class]);
OCMStub([_mockInstallations installationsWithApp:[FIRApp appNamed:self.firebaseAppName]])
.andReturn(_mockInstallations);
FIRInstallationsAuthTokenResult *FISToken =
[[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fake-fis-token" expirationDate:nil];
OCMStub([_mockInstallations
installationIDWithCompletion:([OCMArg invokeBlockWithArgs:FID, [NSNull null], nil])]);
OCMStub([_mockInstallations
authTokenWithCompletion:([OCMArg invokeBlockWithArgs:FISToken, [NSNull null], nil])]);
// Mock the network manager.
FIROptions *options = [[FIROptions alloc] init];
options.projectID = @"test-project-id";
options.APIKey = @"test-api-key";
self.networkManagerMock = OCMClassMock([SEGNetworkManager class]);
OCMStub([self.networkManagerMock
makeAssociationRequestToBackendWithData:[OCMArg any]
token:[OCMArg any]
completion:([OCMArg
invokeBlockWithArgs:@YES, [NSNull null], nil])]);
// Initialize the content manager.
self.contentManager =
[[SEGContentManager alloc] initWithDatabaseManager:[SEGDatabaseManager sharedInstance]
networkManager:self.networkManagerMock];
}
- (void)tearDown {
[self.networkManagerMock stopMocking];
self.networkManagerMock = nil;
self.contentManager = nil;
self.mockIDController = nil;
}
// Associate a fake custom installation id and fake firebase installation id.
// TODO(mandard): check for result and add more tests.
- (void)testAssociateCustomInstallationIdentifierSuccessfully {
XCTestExpectation *expectation =
[self expectationWithDescription:@"associateCustomInstallation for contentmanager"];
[_contentManager
associateCustomInstallationIdentiferNamed:@"my-custom-id"
firebaseApp:self.firebaseAppName
completion:^(BOOL success, NSDictionary *result) {
XCTAssertTrue(success,
@"Could not associate custom installation ID");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
#pragma mark private
- (FIROptions *)FIRAppOptions {
FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
GCMSenderID:@"correct_gcm_sender_id"];
options.APIKey = @"AIzaSaaaaaaaaaaaaaaaaaaaaaaaaaaa1111111";
options.projectID = @"abc-xyz-123";
return options;
}
@end
@@ -0,0 +1,118 @@
// Copyright 2019 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 <XCTest/XCTest.h>
#import "FirebaseSegmentation/Sources/SEGDatabaseManager.h"
#import <OCMock/OCMock.h>
@interface SEGDatabaseManager (Test)
- (NSString *)pathForSegmentationDatabase;
@end
@interface SEGDatabaseManagerTests : XCTestCase {
long _expectationTimeout;
}
@property(nonatomic) id databaseManagerMock;
@end
@implementation SEGDatabaseManagerTests
- (void)setUp {
// Override the database path to create a test database.
self.databaseManagerMock = OCMClassMock([SEGDatabaseManager class]);
OCMStub([self.databaseManagerMock pathForSegmentationDatabase])
.andReturn([self pathForSegmentationTestDatabase]);
// Expectation timeout for each test.
_expectationTimeout = 2;
}
- (void)tearDown {
[self.databaseManagerMock stopMocking];
self.databaseManagerMock = nil;
}
- (void)testDatabaseCreateOrOpen {
XCTestExpectation *expectation = [self expectationWithDescription:@"testDatabaseLoad"];
// Initialize the database manager.
SEGDatabaseManager *databaseManager = [[SEGDatabaseManager alloc] init];
XCTAssertNotNil(databaseManager);
// Load all data from the database.
[databaseManager createOrOpenDatabaseWithCompletion:^(BOOL success, NSDictionary *result) {
XCTAssertTrue(success);
XCTAssertTrue(result.count == 0);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
}
- (void)testDatabaseInsertAndRead {
XCTestExpectation *expectation = [self expectationWithDescription:@"testDatabaseLoad"];
// Initialize the database manager.
SEGDatabaseManager *databaseManager = [[SEGDatabaseManager alloc] init];
XCTAssertNotNil(databaseManager);
// Load all data from the database.
[databaseManager createOrOpenDatabaseWithCompletion:^(BOOL success, NSDictionary *result) {
XCTAssertTrue(success);
XCTAssertTrue(result.count == 0, "Result was %@", result);
// Insert data.
[databaseManager
insertMainTableApplicationNamed:@"firebase_test_app"
customInstanceIdentifier:@"custom-123"
firebaseInstanceIdentifier:@"firebase-123"
associationStatus:kSEGAssociationStatusPending
completionHandler:^(BOOL success, NSDictionary *result) {
XCTAssertTrue(success);
XCTAssertNil(result);
// Read data.
[databaseManager loadMainTableWithCompletion:^(BOOL success,
NSDictionary *result) {
XCTAssertTrue(success);
XCTAssertEqual(result.count, 1);
NSDictionary *associations = [result objectForKey:@"firebase_test_app"];
XCTAssertNotNil(associations);
XCTAssertEqualObjects(
[associations objectForKey:kSEGCustomInstallationIdentifierKey],
@"custom-123");
XCTAssertEqualObjects(
[associations objectForKey:kSEGFirebaseInstallationIdentifierKey],
@"firebase-123");
XCTAssertEqualObjects(
[associations objectForKey:kSEGAssociationStatusKey],
kSEGAssociationStatusPending);
[expectation fulfill];
}];
}];
}];
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
}
#pragma mark Helpers
- (NSString *)pathForSegmentationTestDatabase {
NSArray *dirPaths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportPath = dirPaths.firstObject;
NSString *databaseName =
[NSString stringWithFormat:@"FirebaseSegmentation-test-%d.sqlite3", (arc4random() % 100)];
NSArray *components = @[ appSupportPath, @"Google/FirebaseSegmentation", databaseName ];
NSString *dbPath = [NSString pathWithComponents:components];
NSLog(@"Created test database at: %@", dbPath);
return dbPath;
}
@end
@@ -0,0 +1,53 @@
// Copyright 2019 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 <XCTest/XCTest.h>
#import "FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h"
#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h"
#import "FirebaseSegmentation/Sources/Public/FIRSegmentation.h"
@interface FIRSegmentation (ForTest)
- (instancetype)initWithAppName:(NSString *)appName FIROptions:(FIROptions *)options;
@end
@interface SEGInitializationTests : XCTestCase {
FIRSegmentation *_segmentation;
}
@end
@implementation SEGInitializationTests
- (void)setUp {
FIROptions *options = [[FIROptions alloc] init];
options.APIKey = @"test-api-key";
options.projectID = @"test-firebase-project-id";
_segmentation = [[FIRSegmentation alloc] initWithAppName:@"test-firebase-app-name"
FIROptions:options];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the
// class.
}
- (void)testExample {
[_segmentation setCustomInstallationID:@"test-custom-id"
completion:^(NSError *error){
}];
}
@end
-1
View File
@@ -74,7 +74,6 @@ EXAMPLES:
EOF
}
set -euo pipefail
unset CDPATH
+7 -2
View File
@@ -55,8 +55,9 @@ else
'InAppMessaging|Firebase/InAppMessaging|'\
'FirebaseInAppMessaging.podspec|'\
'Firebase/InstanceID|FirebaseInstanceID.podspec|'\
'FirebaseInstallations'\
'FirebaseCrashlytics.podspec)'\
'FirebaseInstallations|'\
'FirebaseCrashlytics.podspec|'\
'FirebaseSegmentation.podspec)'\
;;
FirebasePod-*)
@@ -146,6 +147,10 @@ else
check_changes '^(FirebaseCore|GoogleUtilities|FirebaseInstallations)'
;;
Segmentation-*)
check_changes '^(Firebase/Core|FirebaseSegmentation|FirebaseSegmentation.podspec)'
;;
*)
echo "Unknown project-method combo" 1>&2
echo " PROJECT=$PROJECT" 1>&2