Skip to content

Commit

Permalink
Data Residency Changes (#973)
Browse files Browse the repository at this point in the history
* Fixed warnings

* Added data residency support.

* Fixed test

Co-authored-by: Brandon Sneed <brandon.sneed@segment.com>
  • Loading branch information
bsneed and Brandon Sneed authored Dec 10, 2020
1 parent c40da41 commit fa0d3c5
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 29 deletions.
6 changes: 3 additions & 3 deletions Segment.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0810;
LastUpgradeCheck = 1200;
LastUpgradeCheck = 1220;
ORGANIZATIONNAME = Segment;
TargetAttributes = {
EADEB85A1DECD080005322DA = {
Expand Down Expand Up @@ -607,7 +607,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down Expand Up @@ -674,7 +674,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down
2 changes: 1 addition & 1 deletion Segment.xcodeproj/xcshareddata/xcschemes/Segment.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
15 changes: 15 additions & 0 deletions Segment/Classes/SEGAnalyticsConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,33 @@ NS_SWIFT_NAME(AnalyticsConfiguration)

/**
* Creates and returns a configuration with default settings and the given write key.
* This will use the API host `https://api.segment.io/v1` as the default.
*
* @param writeKey Your project's write key from segment.io.
*/
+ (_Nonnull instancetype)configurationWithWriteKey:(NSString *_Nonnull)writeKey;

/**
* Creates and returns a configuration with default settings and the given write key.
*
* @param writeKey Your project's write key from segment.io.
* @param defaultAPIHost The default API host to be used if none are supplied from Segment.com
*/
+ (_Nonnull instancetype)configurationWithWriteKey:(NSString *_Nonnull)writeKey defaultAPIHost:(NSURL *_Nullable)defaultAPIHost;

/**
* Your project's write key from segment.io.
*
* @see +configurationWithWriteKey:
*/
@property (nonatomic, copy, readonly, nonnull) NSString *writeKey;

/**
* The API host to be used for network requests to Segment.
* This value can change based on settings obtained from Segment.com.
*/
@property (nonatomic, copy, readonly, nullable) NSURL *apiHost;

/**
* Whether the analytics client should use location services.
* If `YES` and the host app hasn't asked for permission to use location services then the user will be presented with an alert view asking to do so. `NO` by default.
Expand Down
30 changes: 28 additions & 2 deletions Segment/Classes/SEGAnalyticsConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#import "SEGAnalytics.h"
#import "SEGMiddleware.h"
#import "SEGCrypto.h"
#import "SEGHTTPClient.h"
#import "SEGUtils.h"
#if TARGET_OS_IPHONE
@import UIKit;
#elif TARGET_OS_OSX
Expand Down Expand Up @@ -41,20 +43,39 @@ @interface SEGAnalyticsConfiguration ()
@property (nonatomic, strong, readonly) NSMutableArray *factories;
@property (nonatomic, strong) SEGAnalyticsExperimental *experimental;

- (instancetype)initWithWriteKey:(NSString *)writeKey defaultAPIHost:(NSURL * _Nullable)defaultAPIHost;

@end


@implementation SEGAnalyticsConfiguration

+ (instancetype)configurationWithWriteKey:(NSString *)writeKey
{
return [[SEGAnalyticsConfiguration alloc] initWithWriteKey:writeKey];
return [[SEGAnalyticsConfiguration alloc] initWithWriteKey:writeKey defaultAPIHost:nil];
}

+ (instancetype)configurationWithWriteKey:(NSString *)writeKey defaultAPIHost:(NSURL * _Nullable)defaultAPIHost
{
return [[SEGAnalyticsConfiguration alloc] initWithWriteKey:writeKey defaultAPIHost:defaultAPIHost];
}

- (instancetype)initWithWriteKey:(NSString *)writeKey
- (instancetype)initWithWriteKey:(NSString *)writeKey defaultAPIHost:(NSURL * _Nullable)defaultAPIHost
{
if (self = [self init]) {
self.writeKey = writeKey;

// get the host we have stored
NSString *host = [SEGUtils getAPIHost];
if ([host isEqualToString:kSegmentAPIBaseHost]) {
// we're getting the generic host back. have they
// supplied something other than that?
if (defaultAPIHost && ![host isEqualToString:defaultAPIHost.absoluteString]) {
// we should use the supplied default.
host = defaultAPIHost.absoluteString;
[SEGUtils saveAPIHost:host];
}
}
}
return self;
}
Expand Down Expand Up @@ -86,6 +107,11 @@ - (instancetype)init
return self;
}

- (NSURL *)apiHost
{
return [SEGUtils getAPIHostURL];
}

- (void)use:(id<SEGIntegrationFactory>)factory
{
[self.factories addObject:factory];
Expand Down
9 changes: 2 additions & 7 deletions Segment/Classes/SEGHTTPClient.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
@import Foundation;
#import "SEGAnalytics.h"

// TODO: Make this configurable via SEGAnalyticsConfiguration
// NOTE: `/` at the end kind of screws things up. So don't use it
//#define SEGMENT_API_BASE [NSURL URLWithString:@"https://api-segment-io-5fsaj1xnikhp.runscope.net/v1"]
//#define SEGMENT_CDN_BASE [NSURL URLWithString:@"https://cdn-segment-com-5fsaj1xnikhp.runscope.net/v1"]
#define SEGMENT_API_BASE [NSURL URLWithString:@"https://api.segment.io/v1"]
#define SEGMENT_CDN_BASE [NSURL URLWithString:@"https://cdn-settings.segment.com/v1"]

NS_ASSUME_NONNULL_BEGIN

extern NSString * const kSegmentAPIBaseHost;


NS_SWIFT_NAME(HTTPClient)
@interface SEGHTTPClient : NSObject
Expand Down
7 changes: 6 additions & 1 deletion Segment/Classes/SEGHTTPClient.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#import "SEGHTTPClient.h"
#import "NSData+SEGGZIP.h"
#import "SEGAnalyticsUtils.h"
#import "SEGUtils.h"

#define SEGMENT_CDN_BASE [NSURL URLWithString:@"https://cdn-settings.segment.com/v1"]

static const NSUInteger kMaxBatchSize = 475000; // 475KB

NSString * const kSegmentAPIBaseHost = @"https://api.segment.io/v1";

@implementation SEGHTTPClient

+ (NSMutableURLRequest * (^)(NSURL *))defaultRequestFactory
Expand Down Expand Up @@ -72,7 +77,7 @@ - (nullable NSURLSessionUploadTask *)upload:(NSDictionary *)batch forWriteKey:(N
// batch = SEGCoerceDictionary(batch);
NSURLSession *session = [self sessionForWriteKey:writeKey];

NSURL *url = [SEGMENT_API_BASE URLByAppendingPathComponent:@"batch"];
NSURL *url = [[SEGUtils getAPIHostURL] URLByAppendingPathComponent:@"batch"];
NSMutableURLRequest *request = self.requestFactory(url);

// This is a workaround for an IOS 8.3 bug that causes Content-Type to be incorrectly set
Expand Down
2 changes: 0 additions & 2 deletions Segment/Classes/SEGSegmentIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ @interface SEGSegmentIntegration ()
@property (nonatomic, assign) SEGAnalyticsConfiguration *configuration;
@property (atomic, copy) NSDictionary *referrer;
@property (nonatomic, copy) NSString *userId;
@property (nonatomic, strong) NSURL *apiURL;
@property (nonatomic, strong) SEGHTTPClient *httpClient;
@property (nonatomic, strong) id<SEGStorage> fileStorage;
@property (nonatomic, strong) id<SEGStorage> userDefaultsStorage;
Expand Down Expand Up @@ -69,7 +68,6 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics httpClient:(SEGHTTPClient *)ht
self.httpClient.httpSessionDelegate = analytics.oneTimeConfiguration.httpSessionDelegate;
self.fileStorage = fileStorage;
self.userDefaultsStorage = userDefaultsStorage;
self.apiURL = [SEGMENT_API_BASE URLByAppendingPathComponent:@"import"];
self.reachability = [SEGReachability reachabilityWithHostname:@"google.com"];
[self.reachability startNotifier];
self.serialQueue = seg_dispatch_queue_create_specific("io.segment.analytics.segmentio", DISPATCH_QUEUE_SERIAL);
Expand Down
44 changes: 36 additions & 8 deletions Segment/Internal/SEGIntegrationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,12 @@ - (void)setCachedSettings:(NSDictionary *)settings

- (void)updateIntegrationsWithSettings:(NSDictionary *)projectSettings
{
// see if we have a new segment API host and set it.
NSString *apiHost = projectSettings[@"Segment.io"][@"apiHost"];
if (apiHost) {
[SEGUtils saveAPIHost:apiHost];
}

seg_dispatch_specific_sync(_serialQueue, ^{
if (self.initialized) {
return;
Expand Down Expand Up @@ -443,8 +449,29 @@ - (void)configureEdgeFunctions:(NSDictionary *)settings
}
}

- (NSDictionary *)defaultSettings
{
return @{
@"integrations" : @{
@"Segment.io" : @{
@"apiKey" : self.configuration.writeKey,
@"apiHost" : [SEGUtils getAPIHost]
},
},
@"plan" : @{@"track" : @{}}
};
}

- (void)refreshSettings
{
// look at our cache immediately, lets try to get things running
// with the last values while we wait to see about any updates.
NSDictionary *previouslyCachedSettings = [self cachedSettings];
if (previouslyCachedSettings && [previouslyCachedSettings count] > 0) {
[self setCachedSettings:previouslyCachedSettings];
[self configureEdgeFunctions:previouslyCachedSettings];
}

seg_dispatch_specific_async(_serialQueue, ^{
if (self.settingsRequest) {
return;
Expand All @@ -459,23 +486,24 @@ - (void)refreshSettings
NSDictionary *previouslyCachedSettings = [self cachedSettings];
if (previouslyCachedSettings && [previouslyCachedSettings count] > 0) {
[self setCachedSettings:previouslyCachedSettings];
[self configureEdgeFunctions:settings];
[self configureEdgeFunctions:previouslyCachedSettings];
} else if (self.configuration.defaultSettings != nil) {
// If settings request fail, load a user-supplied version if present.
// but make sure segment.io is in the integrations
NSMutableDictionary *newSettings = [self.configuration.defaultSettings serializableMutableDeepCopy];
newSettings[@"integrations"][@"Segment.io"][@"apiKey"] = self.configuration.writeKey;
NSMutableDictionary *integrations = newSettings[@"integrations"];
if (integrations != nil) {
integrations[@"Segment.io"] = @{@"apiKey": self.configuration.writeKey, @"apiHost": [SEGUtils getAPIHost]};
} else {
newSettings[@"integrations"] = @{@"integrations": @{@"apiKey": self.configuration.writeKey, @"apiHost": [SEGUtils getAPIHost]}};
}

[self setCachedSettings:newSettings];
// don't configure edge functions here. it'll do the right thing on it's own.
} else {
// If settings request fail, fall back to using just Segment integration.
// Doesn't address situations where this callback never gets called (though we don't expect that to ever happen).
[self setCachedSettings:@{
@"integrations" : @{
@"Segment.io" : @{@"apiKey" : self.configuration.writeKey},
},
@"plan" : @{@"track" : @{}}
}];
[self setCachedSettings:[self defaultSettings]];
// don't configure edge functions here. it'll do the right thing on it's own.
}
}
Expand Down
1 change: 0 additions & 1 deletion Segment/Internal/SEGState.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedInstance;
- (instancetype)init __unavailable;

- (void)setUserInfo:(SEGUserInfo *)userInfo;
@end

NS_ASSUME_NONNULL_END
4 changes: 4 additions & 0 deletions Segment/Internal/SEGUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(Utilities)
@interface SEGUtils : NSObject

+ (void)saveAPIHost:(nonnull NSString *)apiHost;
+ (nonnull NSString *)getAPIHost;
+ (nullable NSURL *)getAPIHostURL;

+ (NSData *_Nullable)dataFromPlist:(nonnull id)plist;
+ (id _Nullable)plistFromData:(NSData *)data;

Expand Down
30 changes: 30 additions & 0 deletions Segment/Internal/SEGUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#import "SEGAnalyticsConfiguration.h"
#import "SEGReachability.h"
#import "SEGAnalytics.h"
#import "SEGHTTPClient.h"

#include <sys/sysctl.h>

Expand All @@ -15,8 +16,37 @@
static CTTelephonyNetworkInfo *_telephonyNetworkInfo;
#endif

const NSString *segment_apiHost = @"segment_apihost";

@implementation SEGUtils

+ (void)saveAPIHost:(nonnull NSString *)apiHost
{
if (!apiHost) {
return;
}
if (![apiHost containsString:@"https://"]) {
apiHost = [NSString stringWithFormat:@"https://%@", apiHost];
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:apiHost forKey:[segment_apiHost copy]];
}

+ (nonnull NSString *)getAPIHost
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *result = [defaults stringForKey:[segment_apiHost copy]];
if (!result) {
result = kSegmentAPIBaseHost;
}
return result;
}

+ (nullable NSURL *)getAPIHostURL
{
return [NSURL URLWithString:[SEGUtils getAPIHost]];
}

+ (NSData *_Nullable)dataFromPlist:(nonnull id)plist
{
NSError *error = nil;
Expand Down
Loading

0 comments on commit fa0d3c5

Please sign in to comment.