From 855576d8e459c5583cee9d90521222bbb7e22348 Mon Sep 17 00:00:00 2001 From: Ethan Neff Date: Mon, 17 Apr 2017 20:22:58 -0700 Subject: [PATCH] chore: updated iOS to 0.14.12 --- .../Branch-SDK/BNCCommerceEvent.m | 4 +- src/ios/dependencies/Branch-SDK/BNCConfig.m | 7 +- src/ios/dependencies/Branch-SDK/BNCDebug.h | 95 +++ src/ios/dependencies/Branch-SDK/BNCDebug.m | 226 +++++++ .../dependencies/Branch-SDK/BNCDeviceInfo.m | 5 - .../Branch-SDK/BNCFabricAnswers.m | 9 +- src/ios/dependencies/Branch-SDK/BNCLog.h | 223 +++++++ src/ios/dependencies/Branch-SDK/BNCLog.m | 581 ++++++++++++++++++ .../Branch-SDK/BNCPreferenceHelper.h | 2 +- .../Branch-SDK/BNCPreferenceHelper.m | 6 +- .../Branch-SDK/BNCSystemObserver.h | 6 + .../Branch-SDK/BNCSystemObserver.m | 255 +++++--- .../Branch-SDK/BNCXcode7Support.h | 2 + src/ios/dependencies/Branch-SDK/Branch.h | 39 +- src/ios/dependencies/Branch-SDK/Branch.m | 144 +++-- .../Branch-SDK/BranchActivityItemProvider.h | 1 + .../Branch-SDK/BranchActivityItemProvider.m | 33 +- .../dependencies/Branch-SDK/BranchShareLink.h | 112 ++++ .../dependencies/Branch-SDK/BranchShareLink.m | 258 ++++++++ .../Branch-SDK/BranchUniversalObject.h | 3 + .../Branch-SDK/BranchUniversalObject.m | 126 ++-- .../dependencies/Branch-SDK/NSString+Branch.h | 30 + .../dependencies/Branch-SDK/NSString+Branch.m | 47 ++ .../Requests/BranchLoadRewardsRequest.m | 2 + .../Requests/BranchShortUrlRequest.m | 2 +- .../Requests/BranchShortUrlSyncRequest.m | 5 +- 26 files changed, 1980 insertions(+), 243 deletions(-) create mode 100644 src/ios/dependencies/Branch-SDK/BNCDebug.h create mode 100644 src/ios/dependencies/Branch-SDK/BNCDebug.m create mode 100644 src/ios/dependencies/Branch-SDK/BNCLog.h create mode 100644 src/ios/dependencies/Branch-SDK/BNCLog.m create mode 100644 src/ios/dependencies/Branch-SDK/BranchShareLink.h create mode 100644 src/ios/dependencies/Branch-SDK/BranchShareLink.m create mode 100644 src/ios/dependencies/Branch-SDK/NSString+Branch.h create mode 100644 src/ios/dependencies/Branch-SDK/NSString+Branch.m diff --git a/src/ios/dependencies/Branch-SDK/BNCCommerceEvent.m b/src/ios/dependencies/Branch-SDK/BNCCommerceEvent.m index 90115d1f..bf09dd41 100644 --- a/src/ios/dependencies/Branch-SDK/BNCCommerceEvent.m +++ b/src/ios/dependencies/Branch-SDK/BNCCommerceEvent.m @@ -84,7 +84,9 @@ - (NSDictionary*) dictionary { assign(revenue); assign(currency); - assign(transactionID); + if (self.transactionID) { + dictionary[@"transaction_id"] = self.transactionID; + } assign(shipping); assign(tax); assign(coupon); diff --git a/src/ios/dependencies/Branch-SDK/BNCConfig.m b/src/ios/dependencies/Branch-SDK/BNCConfig.m index cc428b18..63b0ce45 100644 --- a/src/ios/dependencies/Branch-SDK/BNCConfig.m +++ b/src/ios/dependencies/Branch-SDK/BNCConfig.m @@ -8,12 +8,7 @@ #include "BNCConfig.h" -#if defined(BNCTesting) -NSString * const BNC_API_BASE_URL = @"https://auhong.api.beta.branch.io"; -#else NSString * const BNC_API_BASE_URL = @"https://api.branch.io"; -#endif - NSString * const BNC_API_VERSION = @"v1"; NSString * const BNC_LINK_URL = @"https://bnc.lt"; -NSString * const BNC_SDK_VERSION = @"0.13.5"; +NSString * const BNC_SDK_VERSION = @"0.14.12"; diff --git a/src/ios/dependencies/Branch-SDK/BNCDebug.h b/src/ios/dependencies/Branch-SDK/BNCDebug.h new file mode 100644 index 00000000..79a17dfd --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BNCDebug.h @@ -0,0 +1,95 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// BNCDebug.h +// Branch.framework +// +// Debugging Support +// Edward Smith, October 2016 +// +// -©- Copyright © 2016 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +//-------------------------------------------------------------------------------------------------- +/** + + BNCDebug + ======== + + # Useful run time debugging environmental variables + + Set DYLD_IMAGE_SUFFIX to _debug to load debug versions of dynamic libraries. + Set NSDebugEnabled to YES to enable obj-c debug checks. + Set NSZombieEnabled to YES to enable zombies to help catch the referencing of released objects. + Set NSAutoreleaseFreedObjectCheckEnabled to YES to catch autorelease problems. + Set MallocStackLoggingNoCompact to YES to track and save all memory allocations. Memory intensive. + + Check NSDebug.h for more debug switches. Also check Technical Note TN2124 and TN2239 for more info. + + Useful exception breakpoints to set: + + objc_exception_throw + NSInternalInconsistencyException + + May be helpful for iPhone Simulator: GTM_DISABLE_IPHONE_LAUNCH_DAEMONS 1 + + Useful lldb macros (Works after Xcode 5.0): + + command script import lldb.macosx.heap + + Search the heap for all references to the pointer 0x0000000116e13920: + + ptr_refs -m 0x0000000116e13920 + +*/ +//-------------------------------------------------------------------------------------------------- + + +#import + + +#ifdef __cplusplus +extern "C" { +#endif + + +///@functiongroup Debugging Functions + + +///@return Returns true if the app is currently attached to a debugger. +extern BOOL BNCDebuggerIsAttached(); + + +///@param object An obj-c instance, class, or meta-class. +///@return Returns an NSString with a dump of the methods and member variables of the instance, +/// class, or meta-class. +extern NSString* _Nonnull BNCDebugStringFromObject(id _Nullable object); + + +///@return Returns the names of all loaded classes as an array of NSStrings. +extern NSArray * _Nonnull BNCDebugArrayOfReqisteredClasses(); + + +///@return Returns an NSString indicating the name of the enclosing method. +#define BNCSStringForCurrentMethod() \ + NSStringFromSelector(_cmd) + + +///@return Returns an NSString indicating the name of the enclosing function. +#define BNCSStringForCurrentFunction() \ + [NSString stringWithFormat:@"%s", __FUNCTION__] + + +/// Stops execution at the current execution point. +/// If attached to a debugger, current app will halt and wait for the debugger. +/// If not attached to a debugger then the current app will probably quit executing. +#define BNCDebugBreakpoint() \ + do { raise(SIGINT); } while (0) + + +#ifdef __cplusplus +} +#endif diff --git a/src/ios/dependencies/Branch-SDK/BNCDebug.m b/src/ios/dependencies/Branch-SDK/BNCDebug.m new file mode 100644 index 00000000..b991c382 --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BNCDebug.m @@ -0,0 +1,226 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// BNCDebug.m +// Branch.framework +// +// Debugging Support +// Edward Smith, October 2016 +// +// -©- Copyright © 2016 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +#import "BNCDebug.h" +#import +#import + + +BOOL BNCDebuggerIsAttached() { + // From an Apple tech note that I've lost --EB Smith + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + if (junk != 0) { + NSLog(@"Program error in BNCDebuggerIsAttached. Junk != 0!"); + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); +} + +NSString * _Nonnull BNCDebugStringFromObject(id _Nullable instance) { + // Dump the class -- + + if (!instance) return @"Object is nil.\n"; + + const char* superclassname = "nil"; + Class class = object_getClass(instance); + Class superclass = class_getSuperclass(class); + if (superclass) superclassname = class_getName(superclass); + if (!superclassname) superclassname = ""; + + NSMutableString *result = [NSMutableString stringWithCapacity:512]; + if (class_isMetaClass(class)) { + [result appendFormat:@"\nClass %p is class '%s' of class '%s':\n", + instance, class_getName(class), superclassname]; + class = instance; + } else { + [result appendFormat:@"\nInstance %p is of class '%s' of class '%s':\n", + instance, class_getName(class), superclassname]; + } + + // Ivars -- + + #define isTypeOf(encoding, type) \ + (strncmp(encoding, @encode(type), strlen(encoding)) == 0) + + #define AppendValueOfType(type, format) \ + if (isTypeOf(encoding, type)) { \ + if (ivarPtr) { \ + [result appendFormat:@"\tIvar '%s' type '%s' value '"format"'.\n", \ + ivarName, #type, *((type*)ivarPtr)]; \ + } else { \ + [result appendFormat:@"\tIvar '%s' type '%s'.\n", \ + ivarName, #type]; \ + } \ + } else + + uint count = 0; + Ivar *ivars = class_copyIvarList(class, &count); + for (int i = 0; i < count; ++i) { + const char* encoding = ivar_getTypeEncoding(ivars[i]); + const char* ivarName = ivar_getName(ivars[i]); + const void* ivarPtr = nil; + if (class == instance) { + // instance is a class, so there aren't any ivar values. + } else if (encoding[0] == '@' || encoding[0] == '#') { + ivarPtr = (__bridge void*) object_getIvar(instance, ivars[i]); + } else { + ivarPtr = (void*) (((__bridge void*)instance) + ivar_getOffset(ivars[i])); + } + + if (encoding[0] == '@') { + if (ivarPtr) + [result appendFormat:@"\tIvar '%s' type '%@' value '%@'.\n", + ivarName, NSStringFromClass(((__bridge id)ivarPtr).class), ivarPtr]; + else { + NSString *className = [NSString stringWithFormat:@"%s", encoding]; + if ([className hasPrefix:@"@\""]) + className = [className substringFromIndex:2]; + if ([className hasSuffix:@"\""]) + className = [className substringToIndex:className.length-1]; + [result appendFormat:@"\tIvar '%s' type '%@'.\n", ivarName, className]; + } + } else if (isTypeOf(encoding, Class)) { + if (ivarPtr) + [result appendFormat:@"\tIvar '%s' type 'class' value '%@'.\n", + ivarName, NSStringFromClass((__bridge Class _Nonnull)(ivarPtr))]; + else + [result appendFormat:@"\tIvar '%s' type 'class'.\n", + ivarName]; + } else if (isTypeOf(encoding, char*)) { + if (ivarPtr) + [result appendFormat:@"\tIvar '%s' type 'char*' value '%s'.\n", + ivarName, *(char**)ivarPtr]; + else + [result appendFormat:@"\tIvar '%s' type 'char*'.\n", + ivarName]; + } else if (isTypeOf(encoding, BOOL)) { + if (ivarPtr) + [result appendFormat:@"\tIvar '%s' type 'BOOL' value '%s'.\n", + ivarName, (*(BOOL*)ivarPtr)?"YES":"NO"]; + else + [result appendFormat:@"\tIvar '%s' type 'BOOL'.\n", + ivarName]; + } else if (isTypeOf(encoding, int)) { + if (ivarPtr) + [result appendFormat:@"\tIvar '%s' type '%s' value '%d'.\n", + ivarName, "int", *((int*)ivarPtr)]; + else + [result appendFormat:@"\tIvar '%s' type '%s'.\n", + ivarName, "int"]; + } else + // clang-format off + AppendValueOfType(float, "%f") + AppendValueOfType(double, "%f") + AppendValueOfType(long double, "%Lf") + AppendValueOfType(char, "%c") + AppendValueOfType(int, "%d") + AppendValueOfType(short, "%hd") + AppendValueOfType(long, "%ld") + AppendValueOfType(long long, "%lld") + AppendValueOfType(unsigned char, "%c") + AppendValueOfType(unsigned int, "%u") + AppendValueOfType(unsigned short, "%hu") + AppendValueOfType(unsigned long, "%lu") + AppendValueOfType(unsigned long long, "%llu") + [result appendFormat:@"\tIvar '%s' type '%s' (un-handled type).\n", + ivarName, + encoding]; + // clang-format off + } + if (ivars) free(ivars); + + #undef AppendValueOfType + #undef isTypeOf + + // Properties -- + + count = 0; + objc_property_t *properties = class_copyPropertyList(class, &count); + for (int i = 0; i < count; ++i) + [result appendFormat:@"\tProperty name: '%s'.\n", property_getName(properties[i])]; + if (properties) free(properties); + + // Class methods -- + + count = 0; + Method *methods = class_copyMethodList(object_getClass(class), &count); + for (int i = 0; i < count; ++i) + [result appendFormat:@"\tClass method name: '%s'.\n", + sel_getName(method_getName(methods[i]))]; + if (methods) free(methods); + + // Instance methods -- + + count = 0; + methods = class_copyMethodList(class, &count); + for (int i = 0; i < count; ++i) + [result appendFormat:@"\tMethod name: '%s'.\n", + sel_getName(method_getName(methods[i]))]; + if (methods) free(methods); + + return result; +} + +NSArray * _Nonnull BNCDebugArrayOfReqisteredClasses() { + // Add all loaded classes to the NSString array: + + int numClasses = 0; + Class * classes = NULL; + + numClasses = objc_getClassList(NULL, 0); + if (numClasses <= 0) return @[]; + + classes = (__unsafe_unretained Class*) malloc(sizeof(Class) * numClasses); + numClasses = objc_getClassList(classes, numClasses); + + NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:numClasses]; + + Class *class = classes; + for (int i = 0; i < numClasses; ++i) { + NSString* s = NSStringFromClass(*class); + if (s) [result addObject:s]; + ++class; + } + + free(classes); + return result; +} diff --git a/src/ios/dependencies/Branch-SDK/BNCDeviceInfo.m b/src/ios/dependencies/Branch-SDK/BNCDeviceInfo.m index 1572b7fe..2f1a6cad 100644 --- a/src/ios/dependencies/Branch-SDK/BNCDeviceInfo.m +++ b/src/ios/dependencies/Branch-SDK/BNCDeviceInfo.m @@ -176,12 +176,7 @@ + (NSString*) userAgentString { dispatch_async(dispatch_get_main_queue(), agentBlock); dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, timeoutDelta); - #if defined(BNCTesting) - long result = dispatch_block_wait(agentBlock, timeoutTime); - NSLog(@"Wait result: %ld.", result); - #else dispatch_block_wait(agentBlock, timeoutTime); - #endif retries--; } return browserUserAgentString; diff --git a/src/ios/dependencies/Branch-SDK/BNCFabricAnswers.m b/src/ios/dependencies/Branch-SDK/BNCFabricAnswers.m index 84404b55..962e747c 100644 --- a/src/ios/dependencies/Branch-SDK/BNCFabricAnswers.m +++ b/src/ios/dependencies/Branch-SDK/BNCFabricAnswers.m @@ -41,9 +41,12 @@ + (NSDictionary *)prepareBranchDataForAnswers:(NSDictionary *)dictionary { temp[@"referring_branch_identity"] = dictionary[key]; } } - - BNCPreferenceHelper *preferenceHelper = [BNCPreferenceHelper preferenceHelper]; - temp[@"branch_identity"] = preferenceHelper.identityID; + + [[BNCPreferenceHelper preferenceHelper] synchronize]; + NSString *identity = [[BNCPreferenceHelper preferenceHelper].identityID copy]; + if (identity) { + temp[@"branch_identity"] = identity; + } return temp; } diff --git a/src/ios/dependencies/Branch-SDK/BNCLog.h b/src/ios/dependencies/Branch-SDK/BNCLog.h new file mode 100644 index 00000000..83ae1346 --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BNCLog.h @@ -0,0 +1,223 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// BNCLog.h +// Branch.framework +// +// Simple logging functions +// Edward Smith, October 2016 +// +// -©- Copyright © 2016 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +#import +#import "BNCDebug.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +///@functiongroup Branch Logging Functions + + +#pragma mark Log Message Severity + +/// Log message severity +typedef NS_ENUM(NSInteger, BNCLogLevel) { + BNCLogLevelAll = 0, + BNCLogLevelDebug = BNCLogLevelAll, + BNCLogLevelBreakPoint, + BNCLogLevelWarning, + BNCLogLevelError, + BNCLogLevelAssert, + BNCLogLevelLog, + BNCLogLevelNone, + BNCLogLevelMax +}; + +/*! +* @return Returns the current log severity display level. +*/ +extern BNCLogLevel BNCLogDisplayLevel(); + +/*! +* @param level Sets the current display level for log messages. +*/ +extern void BNCLogSetDisplayLevel(BNCLogLevel level); + + +#pragma mark - Log Message Synchronization + + +/*! +* @discussion When log messages are synchronized they are written to the log in order, including +* across separate threads. Synchronizing log messages usually improves performance since it +* reduces global resource lock contention. Note that synchronization has the side effect of some +* messages not being available immediately since they are written on a separate thread. +* +* @param enable Enable log message synchronization. +*/ +extern void BNCLogSetSynchronizeMessages(BOOL enable); + +/*!@return Returns YES if log messages are synchronized between threads. +*/ +extern BOOL BNCLogSynchronizeMessages(); + + +#pragma mark - Programmatic Breakpoints + + +///@return Returns 'YES' if programmatic breakpoints are enabled. +extern BOOL BNCLogBreakPointsAreEnabled(); + +///@param enabled Sets programmatic breakpoints enabled or disabled. +extern void BNCLogSetBreakPointsEnabled(BOOL enabled); + + +#pragma mark - Optional Log Output Handlers + + +///@info Pre-defined log message handlers -- + +typedef void (*BNCLogOutputFunctionPtr)(NSDate*_Nonnull timestamp, BNCLogLevel level, NSString*_Nullable message); + +extern void BNCLogFunctionOutputToStdOut(NSDate*_Nonnull timestamp, BNCLogLevel level, NSString *_Nullable message); +extern void BNCLogFunctionOutputToStdErr(NSDate*_Nonnull timestamp, BNCLogLevel level, NSString *_Nullable message); + +///@param functionPtr A pointer to the logging function. Setting the parameter to NULL will flush +/// and close the currently set log function and future log messages will be +/// ignored until a non-NULL logging function is set. +extern void BNCLogSetOutputFunction(BNCLogOutputFunctionPtr _Nullable functionPtr); + +///@return Returns the current logging function. +extern BNCLogOutputFunctionPtr _Nullable BNCLogOutputFunction(); + +///@param URL Sets the log output function to a function that writes messages to the file at URL. +extern void BNCLogSetOutputToURL(NSURL *_Nullable URL); + +///@param URL Sets the log output function to a function that writes messages to the file at URL. +///@param maxRecords Wraps the file at `maxRecords` records. +extern void BNCLogSetOutputToURLRecordWrap(NSURL *_Nullable URL, long maxRecords); + +///@param URL Sets the log output function to a function that writes messages to the file at URL. +///@param maxBytes Wraps the file at `maxBytes` bytes. Must be an even number of bytes. +extern void BNCLogSetOutputToURLByteWrap(NSURL *_Nullable URL, long maxBytes); + +typedef void (*BNCLogFlushFunctionPtr)(); + +///@param flushFunction The logging functions use `flushFunction` to flush the outstanding log +/// messages to the output function. For instance, this function may call +/// `fsync` to assure that the log messages are written to disk. +extern void BNCLogSetFlushFunction(BNCLogFlushFunctionPtr _Nullable flushFunction); + +///@return Returns the current flush function. +extern BNCLogFlushFunctionPtr _Nullable BNCLogFlushFunction(); + + +#pragma mark - BNCLogMessageInternal + + +/// The main logging function used in the logging defines. +extern void BNCLogMessageInternal( + BNCLogLevel logLevel, + const char *_Nullable sourceFileName, + int sourceLineNumber, + id _Nullable messageFormat, + ... +); + +/// This function synchronizes all outstanding log messages and writes them to the logging function +/// set by BNCLogSetOutputFunction. +extern void BNCLogFlushMessages(); + + +#pragma mark - Logging +///@info Logging + +///@param format Log a debug message with the specified formatting. +#define BNCLogDebug(...) \ + do { BNCLogMessageInternal(BNCLogLevelDebug, __FILE__, __LINE__, __VA_ARGS__); } while (0) + +///@param format Log a warning message with the specified formatting. +#define BNCLogWarning(...) \ + do { BNCLogMessageInternal(BNCLogLevelWarning, __FILE__, __LINE__, __VA_ARGS__); } while (0) + +///@param format Log an error message with the specified formatting. +#define BNCLogError(...) \ + do { BNCLogMessageInternal(BNCLogLevelError, __FILE__, __LINE__, __VA_ARGS__); } while (0) + +///@param format Log a message with the specified formatting. +#define BNCLog(...) \ + do { BNCLogMessageInternal(BNCLogLevelLog, __FILE__, __LINE__, __VA_ARGS__); } while (0) + +///Cause a programmatic breakpoint if breakpoints are enabled. +#define BNCLogBreakPoint() \ + do { \ + if (BNCLogBreakPointsAreEnabled()) { \ + BNCLogMessageInternal(BNCLogLevelBreakPoint, __FILE__, __LINE__, @"Programmatic breakpoint."); \ + if (BNCDebuggerIsAttached()) { \ + BNCLogFlushMessages(); \ + BNCDebugBreakpoint(); \ + } \ + } \ + } while (0) + +///Log a message and cause a programmatic breakpoint if breakpoints are enabled. +#define BNCBreakPointWithMessage(...) \ + do { \ + if (BNCLogBreakPointsAreEnabled() { \ + BNCLogMessageInternal(BNCLogLevelBreakPoint, __FILE__, __LINE__, __VA_ARGS__); \ + if (BNCDebuggerIsAttached()) { \ + BNCLogFlushMessages(); \ + BNCDebugBreakpoint(); \ + } \ + } \ + } while (0) + +///Check if an asserting is true. If programmatic breakpoints are enabled then break. +#define BNCLogAssert(condition) \ + do { \ + if (!(condition)) { \ + BNCLogMessageInternal(BNCLogLevelAssert, __FILE__, __LINE__, @"(%s) !!!", #condition); \ + if (BNCLogBreakPointsAreEnabled() && BNCDebuggerIsAttached()) { \ + BNCLogFlushMessages(); \ + BNCDebugBreakpoint(); \ + } \ + } \ + } while (0) + +///Check if an asserting is true logging a message if the assertion fails. +///If programmatic breakpoints are enabled then break. +#define BNCLogAssertWithMessage(condition, message, ...) \ + do { \ + if (!(condition)) { \ + NSString *m = [NSString stringWithFormat:message, __VA_ARGS__]; \ + BNCLogMessageInternal(BNCLogLevelAssert, __FILE__, __LINE__, @"(%s) !!! %@", #condition, m); \ + if (BNCLogBreakPointsAreEnabled() && BNCDebuggerIsAttached()) { \ + BNCLogFlushMessages(); \ + BNCDebugBreakpoint(); \ + } \ + } \ + } while (0) + +///Assert that the current thread is the main thread. +#define BNCLogAssertIsMainThread() \ + BNCLogAssert([NSThread isMainThread]) + +///Write the name of the current method to the log. +#define BNCLogMethodName() \ + BNCLogDebug(@"Method '%@'.", NSStringFromSelector(_cmd)) + +///Write the name of the current function to the log. +#define BNCLogFunctionName() \ + BNCLogDebug(@"Function '%s'.", __FUNCTION__) + + +#ifdef __cplusplus +} +#endif diff --git a/src/ios/dependencies/Branch-SDK/BNCLog.m b/src/ios/dependencies/Branch-SDK/BNCLog.m new file mode 100644 index 00000000..1def862c --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BNCLog.m @@ -0,0 +1,581 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// BNCLog.m +// Branch.framework +// +// Simple logging functions +// Edward Smith, October 2016 +// +// -©- Copyright © 2016 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +#import "BNCLog.h" + + +#define _countof(array) (sizeof(array)/sizeof(array[0])) +static NSNumber *bnc_LogIsInitialized = nil; + +// An 'inner', last attempt at logging if an error occurs in BNCLog. +// BNCLog can't log itself, but if an error occurs it uses this simple define: + +extern void BNCLogInternalErrorFunction(int linenumber, NSString*format, ...); +void BNCLogInternalErrorFunction(int linenumber, NSString*format, ...) { + + va_list args; + va_start(args, format); + NSString* message = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + NSLog(@"[branch.io] BNCLog.m (%d) Log error: %@", linenumber, message); +} + +#define BNCLogInternalError(...) \ + BNCLogInternalErrorFunction(__LINE__, __VA_ARGS__) + + +#pragma mark - Default Output Functions + +static int bnc_LogDescriptor = -1; + +void BNCLogFunctionOutputToStdOut( + NSDate*_Nonnull timestamp, + BNCLogLevel level, + NSString *_Nullable message + ) { + NSData *data = [message dataUsingEncoding:NSNEXTSTEPStringEncoding]; + if (!data) data = [@"" dataUsingEncoding:NSNEXTSTEPStringEncoding]; + long n = write(STDOUT_FILENO, data.bytes, data.length); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't write log message (%d): %s.", e, strerror(e)); + } + write(STDOUT_FILENO, "\n", sizeof('\n')); +} + +void BNCLogFunctionOutputToStdErr( + NSDate*_Nonnull timestamp, + BNCLogLevel level, + NSString *_Nullable message + ) { + NSData *data = [message dataUsingEncoding:NSNEXTSTEPStringEncoding]; + if (!data) data = [@"" dataUsingEncoding:NSNEXTSTEPStringEncoding]; + long n = write(STDERR_FILENO, data.bytes, data.length); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't write log message (%d): %s.", e, strerror(e)); + } + write(STDERR_FILENO, "\n", sizeof('\n')); +} + +void BNCLogFunctionOutputToFileDescriptor( + NSDate*_Nonnull timestamp, + BNCLogLevel level, + NSString *_Nullable message + ) { + // Pad length to even characters + if (!message) message = @""; + NSString *string = [NSString stringWithFormat:@"%@\n", message]; + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + if ((data.length & 1) == 1) { + string = [NSString stringWithFormat:@"%@ \n", message]; + data = [string dataUsingEncoding:NSUTF8StringEncoding]; + } + if ((data.length & 1) != 0) { + BNCLogInternalError(@"Writing un-even bytes!"); + } + long n = write(bnc_LogDescriptor, data.bytes, data.length); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't write log message (%d): %s.", e, strerror(e)); + } +} + +void BNCLogFlushFileDescriptor() { + if (bnc_LogDescriptor >= 0) { + fsync(bnc_LogDescriptor); + } +} + +void BNCLogSetOutputToURL(NSURL *_Nullable url) { + if (url == nil) return; + BNCLogSetOutputFunction(BNCLogFunctionOutputToFileDescriptor); + BNCLogSetFlushFunction(BNCLogFlushFileDescriptor); + bnc_LogDescriptor = open( + url.path.UTF8String, + O_RDWR|O_CREAT|O_APPEND, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP + ); + if (bnc_LogDescriptor < 0) { + int e = errno; + BNCLogInternalError(@"Can't open log file '%@'.", url); + BNCLogInternalError(@"Can't open log file (%d): %s.", e, strerror(e)); + } +} + +#pragma mark - Record Wrap Output File Functions + +static off_t bnc_LogOffset = 0; +static off_t bnc_LogOffsetMax = 100; +static off_t bnc_LogRecordSize = 1024; +static NSDateFormatter *bnc_LogDateFormatter = nil; + +void BNCLogRecordWrapWrite(NSDate*_Nonnull timestamp, BNCLogLevel level, NSString *_Nullable message) { + + NSString * string = [NSString stringWithFormat:@"%@ %ld %@", + [bnc_LogDateFormatter stringFromDate:timestamp], (long) level, message]; + NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + + char buffer[bnc_LogRecordSize]; + memset(buffer, ' ', sizeof(buffer)); + buffer[sizeof(buffer)-1] = '\n'; + long len = MIN(stringData.length, sizeof(buffer)-1); + memcpy(buffer, stringData.bytes, len); + + off_t n = write(bnc_LogDescriptor, buffer, sizeof(buffer)); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't write log message (%d): %s.", e, strerror(e)); + } + bnc_LogOffset++; + if (bnc_LogOffset >= bnc_LogOffsetMax) { + bnc_LogOffset = 0; + n = lseek(bnc_LogDescriptor, 0, SEEK_SET); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log (%d): %s.", e, strerror(e)); + } + } +} + +void BNCLogRecordWrapFlush() { + if (bnc_LogDescriptor >= 0) { + fsync(bnc_LogDescriptor); + } +} + +BOOL BNCLogRecordWrapOpenURL(NSURL *url, long maxRecords, long recordSize) { + if (url == nil) return NO; + bnc_LogOffsetMax = MAX(1, maxRecords); + bnc_LogRecordSize = MAX(64, recordSize); + if ((bnc_LogRecordSize & 1) != 0) { + // Can't have odd-length records. + bnc_LogRecordSize++; + } + BNCLogSetOutputFunction(BNCLogRecordWrapWrite); + BNCLogSetFlushFunction(BNCLogRecordWrapFlush); + bnc_LogDescriptor = open( + url.path.UTF8String, + O_RDWR|O_CREAT, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP + ); + if (bnc_LogDescriptor < 0) { + int e = errno; + BNCLogInternalError(@"Can't open log file '%@'.", url); + BNCLogInternalError(@"Can't open log file (%d): %s.", e, strerror(e)); + return NO; + } + + // Truncate the file if the file size > max file size. + + off_t n = 0; + off_t maxSz = bnc_LogOffsetMax * bnc_LogRecordSize; + off_t sz = lseek(bnc_LogDescriptor, 0, SEEK_END); + if (sz < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log (%d): %s.", e, strerror(e)); + } else if (sz > maxSz) { + n = ftruncate(bnc_LogDescriptor, maxSz); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't truncate log (%d): %s.", e, strerror(e)); + } + } + lseek(bnc_LogDescriptor, 0, SEEK_SET); + + // Read the records until the oldest record is found -- + + off_t oldestOffset = 0; + NSDate * oldestDate = [NSDate distantFuture]; + + off_t offset = 0; + char buffer[bnc_LogRecordSize]; + n = read(bnc_LogDescriptor, &buffer, sizeof(buffer)); + while (n == sizeof(buffer)) { + NSString *dateString = + [[NSString alloc] initWithBytes:buffer length:27 encoding:NSUTF8StringEncoding]; + NSDate *date = [bnc_LogDateFormatter dateFromString:dateString]; + if (date && [date compare:oldestDate] < 0) { + oldestOffset = offset; + oldestDate = date; + } + offset++; + n = read(bnc_LogDescriptor, &buffer, sizeof(buffer)); + } + if (offset < bnc_LogOffsetMax) + bnc_LogOffset = offset; + else + if (oldestOffset >= bnc_LogOffsetMax) + bnc_LogOffset = 0; + else + bnc_LogOffset = oldestOffset; + n = lseek(bnc_LogDescriptor, bnc_LogOffset*bnc_LogRecordSize, SEEK_SET); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log (%d): %s.", e, strerror(e)); + } + return YES; +} + +void BNCLogSetOutputToURLRecordWrapSize(NSURL *_Nullable url, long maxRecords, long recordSize) { + BNCLogRecordWrapOpenURL(url, maxRecords, recordSize); +} + +void BNCLogSetOutputToURLRecordWrap(NSURL *_Nullable url, long maxRecords) { + BNCLogSetOutputToURLRecordWrapSize(url, maxRecords, 1024); +} + +#pragma mark - Byte Wrap Output File Functions + +void BNCLogByteWrapWrite(NSDate*_Nonnull timestamp, BNCLogLevel level, NSString *_Nullable message) { + + NSString * string = [NSString stringWithFormat:@"%@ %ld %@\n", + [bnc_LogDateFormatter stringFromDate:timestamp], (long) level, message]; + NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + + if ((stringData.length & 1) != 0) { + string = [NSString stringWithFormat:@"%@ %ld %@ \n", + [bnc_LogDateFormatter stringFromDate:timestamp], (long) level, message]; + stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + } + + // Truncate the file if the file size > max file size. + + if ((bnc_LogOffset + stringData.length) > bnc_LogOffsetMax) { + long n = ftruncate(bnc_LogDescriptor, bnc_LogOffset); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't truncate log (%d): %s.", e, strerror(e)); + } + lseek(bnc_LogDescriptor, 0, SEEK_SET); + bnc_LogOffset = 0; + } + + long n = write(bnc_LogDescriptor, stringData.bytes, stringData.length); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't write log message (%d): %s.", e, strerror(e)); + } else { + bnc_LogOffset += n; + } +} + +void BNCLogByteWrapFlush() { + if (bnc_LogDescriptor >= 0) { + fsync(bnc_LogDescriptor); + } +} + +NSString *BNCLogByteWrapReadNextRecord() { + + char *buffer = NULL; + long bufferSize = 0; + off_t originalOffset = lseek(bnc_LogDescriptor, 0, SEEK_CUR); + if (originalOffset < 0) { + int e = errno; + BNCLogInternalError(@"Can't find offset in log file (%d): %s.", e, strerror(e)); + goto error_exit; + } + + do { + + bufferSize += 1024; + if (buffer) free(buffer); + buffer = malloc(bufferSize); + if (!buffer) { + BNCLogInternalError(@"Can't allocate a buffer of %ld bytes.", bufferSize); + goto error_exit; + } + + off_t n = lseek(bnc_LogDescriptor, originalOffset, SEEK_SET); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log file (%d): %s.", e, strerror(e)); + goto error_exit; + } + n = read(bnc_LogDescriptor, buffer, bufferSize); + if (n == 0) { + goto error_exit; + } else if (n < 0) { + int e = errno; + if (e != EOF) { + BNCLogInternalError(@"Can't read log message (%d): %s.", e, strerror(e)); + } + goto error_exit; + } + + char* p = buffer; + while ( (p-buffer) < n && *p != '\n') { + p++; + } + if (*p == '\n') { + long offset = (p-buffer)+1; + NSString *result = [[NSString alloc] + initWithBytes:buffer length:offset encoding:NSUTF8StringEncoding]; + bnc_LogOffset = originalOffset + offset; + n = lseek(bnc_LogDescriptor, bnc_LogOffset, SEEK_SET); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log file (%d): %s.", e, strerror(e)); + } + free(buffer); + return result; + } + + } while (bufferSize < 1024*20); + +error_exit: + if (buffer) free(buffer); + return nil; +} + +BOOL BNCLogByteWrapOpenURL(NSURL *url, long maxBytes) { + if (url == nil) return NO; + bnc_LogOffsetMax = MAX(256, maxBytes); + BNCLogSetOutputFunction(BNCLogByteWrapWrite); + BNCLogSetFlushFunction(BNCLogByteWrapFlush); + bnc_LogDescriptor = open( + url.path.UTF8String, + O_RDWR|O_CREAT, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP + ); + if (bnc_LogDescriptor < 0) { + int e = errno; + BNCLogInternalError(@"Can't open log file '%@'.", url); + BNCLogInternalError(@"Can't open log file (%d): %s.", e, strerror(e)); + return NO; + } + + // Truncate the file if the file size > max file size. + + off_t n = 0; + off_t maxSz = bnc_LogOffsetMax; + off_t sz = lseek(bnc_LogDescriptor, 0, SEEK_END); + if (sz < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log (%d): %s.", e, strerror(e)); + } else if (sz > maxSz) { + n = ftruncate(bnc_LogDescriptor, maxSz); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't truncate log (%d): %s.", e, strerror(e)); + } + } + bnc_LogOffset = 0; + lseek(bnc_LogDescriptor, bnc_LogOffset, SEEK_SET); + + // Read the records until the oldest record is found -- + + BOOL logDidWrap = NO; + off_t wrapOffset = 0; + + off_t lastOffset = 0; + NSDate *lastDate = [NSDate distantPast]; + + NSString *record = BNCLogByteWrapReadNextRecord(); + while (record) { + NSString *dateString = [record substringWithRange:NSMakeRange(0, 27)]; + NSDate *date = [bnc_LogDateFormatter dateFromString:dateString]; + if (!date || [date compare:lastDate] < 0) { + wrapOffset = lastOffset; + logDidWrap = YES; + } + lastDate = date ?: [NSDate distantPast]; + lastOffset = bnc_LogOffset; + record = BNCLogByteWrapReadNextRecord(); + } + if (logDidWrap) { + bnc_LogOffset = wrapOffset; + } else if (bnc_LogOffset >= bnc_LogOffsetMax) + bnc_LogOffset = 0; + n = lseek(bnc_LogDescriptor, bnc_LogOffset, SEEK_SET); + if (n < 0) { + int e = errno; + BNCLogInternalError(@"Can't seek in log (%d): %s.", e, strerror(e)); + } + return YES; +} + +void BNCLogSetOutputToURLByteWrap(NSURL *_Nullable URL, long maxBytes) { + BNCLogByteWrapOpenURL(URL, maxBytes); +} + +#pragma mark - Log Message Severity + +static BNCLogLevel bnc_LogDisplayLevel = BNCLogLevelWarning; + +BNCLogLevel BNCLogDisplayLevel() { + @synchronized (bnc_LogIsInitialized) { + return bnc_LogDisplayLevel; + } +} + +void BNCLogSetDisplayLevel(BNCLogLevel level) { + @synchronized (bnc_LogIsInitialized) { + bnc_LogDisplayLevel = level; + } +} + +#pragma mark - Log Synchronization + +static BOOL bnc_SynchronizeMessages = YES; + +void BNCLogSetSynchronizeMessages(BOOL enable) { + @synchronized (bnc_LogIsInitialized) { + bnc_SynchronizeMessages = enable; + } +} + +BOOL BNCLogSynchronizeMessages() { + @synchronized (bnc_LogIsInitialized) { + return bnc_SynchronizeMessages; + } +} + +#pragma mark - Break Points + +static BOOL bnc_LogBreakPointsAreEnabled = NO; + +BOOL BNCLogBreakPointsAreEnabled() { + @synchronized (bnc_LogIsInitialized) { + return bnc_LogBreakPointsAreEnabled; + } +} + +void BNCLogSetBreakPointsEnabled(BOOL enabled) { + @synchronized (bnc_LogIsInitialized) { + bnc_LogBreakPointsAreEnabled = enabled; + } +} + +#pragma mark - Log Functions + +static BNCLogOutputFunctionPtr bnc_LoggingFunction = BNCLogFunctionOutputToStdOut; +static BNCLogFlushFunctionPtr bnc_LogFlushFunction = BNCLogFlushFileDescriptor; + +BNCLogOutputFunctionPtr _Nullable BNCLogOutputFunction() { + @synchronized (bnc_LogIsInitialized) { + return bnc_LoggingFunction; + } +} + +void BNCLogSetOutputFunction(BNCLogOutputFunctionPtr _Nullable logFunction) { + @synchronized (bnc_LogIsInitialized) { + BNCLogFlushMessages(); + if (bnc_LogDescriptor >= 0) + close(bnc_LogDescriptor); + bnc_LogDescriptor = -1; + bnc_LoggingFunction = logFunction; + } +} + +BNCLogFlushFunctionPtr BNCLogFlushFunction() { + @synchronized (bnc_LogIsInitialized) { + return bnc_LogFlushFunction; + } +} + +void BNCLogSetFlushFunction(BNCLogFlushFunctionPtr flushFunction) { + @synchronized (bnc_LogIsInitialized) { + bnc_LogFlushFunction = flushFunction; + } +} + +#pragma mark - BNCLogInternal + +static dispatch_queue_t bnc_LogQueue = nil; + +void BNCLogMessageInternal( + BNCLogLevel logLevel, + const char *_Nullable file, + int lineNumber, + NSString *_Nullable message, + ... + ) { + if (!file) file = ""; + if (!message) message = @""; + if (![message isKindOfClass:[NSString class]]) { + message = [NSString stringWithFormat:@"0x%016llx <%@> %@", + (uint64_t) message, message.class, message.description]; + } + + NSString* filename = + [[NSString stringWithCString:file encoding:NSMacOSRomanStringEncoding] + lastPathComponent]; + + NSString *logLevels[BNCLogLevelMax] = { + @" Debug", + @" Break", + @"Warning", + @" Error", + @" Assert", + @" Log", + @" None", + }; + + logLevel = MAX(MIN(logLevel, BNCLogLevelMax-1), 0); + NSString *levelString = logLevels[logLevel]; + + va_list args; + va_start(args, message); + NSString* m = [[NSString alloc] initWithFormat:message arguments:args]; + NSString* s = [NSString stringWithFormat: + @"[branch.io] %@(%d) %@: %@", filename, lineNumber, levelString, m]; + va_end(args); + + if (logLevel >= bnc_LogDisplayLevel) { + NSLog(@"%@", s); + } + + if (BNCLogSynchronizeMessages()) { + dispatch_async(bnc_LogQueue, ^{ + if (bnc_LoggingFunction) + bnc_LoggingFunction([NSDate date], logLevel, s); + }); + } else { + if (bnc_LoggingFunction) + bnc_LoggingFunction([NSDate date], logLevel, s); + } +} + +void BNCLogFlushMessages() { + if (BNCLogSynchronizeMessages()) { + dispatch_sync(bnc_LogQueue, ^{ + if (bnc_LogFlushFunction) + bnc_LogFlushFunction(); + }); + } else { + if (bnc_LogFlushFunction) + bnc_LogFlushFunction(); + } +} + +#pragma mark - BNCLogInitialize + +void BNCLogInitialize() __attribute__((constructor)); +void BNCLogInitialize() { + static dispatch_once_t onceToken = 0; + dispatch_once(&onceToken, ^ { + bnc_LogQueue = dispatch_queue_create("io.branch.log", DISPATCH_QUEUE_SERIAL); + + bnc_LogDateFormatter = [[NSDateFormatter alloc] init]; + bnc_LogDateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + bnc_LogDateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSSSSX"; + bnc_LogDateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + + bnc_LogIsInitialized = @(YES); + }); +} diff --git a/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.h b/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.h index 21b1c9d5..fe51255d 100644 --- a/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.h +++ b/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.h @@ -83,6 +83,6 @@ - (NSDictionary *)getContentAnalyticsManifest; - (void)saveContentAnalyticsManifest:(NSDictionary *)cdManifest; -- (void) save; // Flushes preference queue to persistence. +- (void) synchronize; // Flushes preference queue to persistence. @end diff --git a/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.m b/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.m index 134758f0..fcdcdbde 100644 --- a/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.m +++ b/src/ios/dependencies/Branch-SDK/BNCPreferenceHelper.m @@ -154,13 +154,13 @@ - (NSOperationQueue *)persistPrefsQueue { } } -- (void) save { +- (void) synchronize { // Flushes preference queue to persistence. [_persistPrefsQueue waitUntilAllOperationsAreFinished]; } - (void) dealloc { - [self save]; + [self synchronize]; } #pragma mark - Debug methods @@ -752,7 +752,7 @@ - (void)persistPrefsToDisk { [self logWarning:@"Can't create preferences data."]; return; } - NSURL *prefsURL = self.class.URLForPrefsFile; + NSURL *prefsURL = [self.class.URLForPrefsFile copy]; NSBlockOperation *newPersistOp = [NSBlockOperation blockOperationWithBlock:^ { NSError *error = nil; [data writeToURL:prefsURL options:NSDataWritingAtomic error:&error]; diff --git a/src/ios/dependencies/Branch-SDK/BNCSystemObserver.h b/src/ios/dependencies/Branch-SDK/BNCSystemObserver.h index fbe1e531..6589023c 100644 --- a/src/ios/dependencies/Branch-SDK/BNCSystemObserver.h +++ b/src/ios/dependencies/Branch-SDK/BNCSystemObserver.h @@ -8,6 +8,12 @@ #import +typedef NS_ENUM(NSInteger, BNCUpdateState) { + BNCUpdateStateInstall = 0, // App was recently installed. + BNCUpdateStateNonUpdate = 1, // App was neither newly installed nor updated. + BNCUpdateStateUpdate = 2, // App was recently updated. +}; + @interface BNCSystemObserver : NSObject + (NSString *)getUniqueHardwareId:(BOOL *)isReal isDebug:(BOOL)debug andType:(NSString **)type; diff --git a/src/ios/dependencies/Branch-SDK/BNCSystemObserver.m b/src/ios/dependencies/Branch-SDK/BNCSystemObserver.m index 6b771188..559ee7c8 100644 --- a/src/ios/dependencies/Branch-SDK/BNCSystemObserver.m +++ b/src/ios/dependencies/Branch-SDK/BNCSystemObserver.m @@ -118,135 +118,194 @@ + (NSString *)getModel { } + (BOOL)isSimulator { - UIDevice *currentDevice = [UIDevice currentDevice]; - NSString *device; - if ([BNCSystemObserver getOSVersion].integerValue >= 9) { - device = currentDevice.name; - } - else { - device = currentDevice.model; - } - return [device rangeOfString:@"Simulator"].location != NSNotFound; + #if (TARGET_OS_SIMULATOR) + return YES; + #else + return NO; + #endif } -typedef NS_ENUM(NSInteger, BNCUpdateStatus) { - BNCUpdateStatusInstall = 0, // App was recently installed. - BNCUpdateStatusNonUpdate = 1, // App was neither newly installed nor updated. - BNCUpdateStatusUpdate = 2, // App was recently updated. -}; ++ (NSString *)getOS { + return @"iOS"; +} + ++ (NSString *)getOSVersion { + UIDevice *device = [UIDevice currentDevice]; + return [device systemVersion]; +} -+ (NSNumber*) getUpdateState_10_2_2 { ++ (NSNumber *)getScreenWidth { + UIScreen *mainScreen = [UIScreen mainScreen]; + float scaleFactor = mainScreen.scale; + CGFloat width = mainScreen.bounds.size.width * scaleFactor; + return [NSNumber numberWithInteger:(NSInteger)width]; +} ++ (NSNumber *)getScreenHeight { + UIScreen *mainScreen = [UIScreen mainScreen]; + float scaleFactor = mainScreen.scale; + CGFloat height = mainScreen.bounds.size.height * scaleFactor; + return [NSNumber numberWithInteger:(NSInteger)height]; +} + +#pragma mark - getUpdateState Suite + ++ (NSNumber*) getUpdateState { + + NSDate * buildDate = [self appBuildDate]; + NSDate * appInstallDate = [self appInstallDate]; NSString *storedAppVersion = [BNCPreferenceHelper preferenceHelper].appVersion; - NSString *currentAppVersion = [BNCSystemObserver getAppVersion]; + NSString *currentAppVersion = [self getAppVersion]; + + BNCUpdateState result = + [self updateStateWithBuildDate:buildDate + appInstallDate:appInstallDate + storedAppVersion:storedAppVersion + currentAppVersion:currentAppVersion]; + +#if 0 // Display an alert for testing. Only for debugging. + NSString *message = @"No result."; + switch (result) { + case BNCUpdateStateInstall: message = @"New install."; break; + case BNCUpdateStateNonUpdate: message = @"Non-update."; break; + case BNCUpdateStateUpdate: message = @"App update."; break; + default: message = @"Invalid value."; break; + } + message = + [NSString stringWithFormat:@"iOS: %@\n%@", + [UIDevice currentDevice].systemVersion, + message]; + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertView *alert = + [[UIAlertView alloc] + initWithTitle:@"Update State" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + }); +#endif + + return @(result); +} + ++ (BNCUpdateState) updateStateWithBuildDate:(NSDate*)buildDate + appInstallDate:(NSDate*)appInstallDate + storedAppVersion:(NSString*)storedAppVersion + currentAppVersion:(NSString*)currentAppVersion { if (storedAppVersion) { - if ([storedAppVersion isEqualToString:currentAppVersion]) - return @(BNCUpdateStatusNonUpdate); + if (currentAppVersion && [storedAppVersion isEqualToString:currentAppVersion]) + return BNCUpdateStateNonUpdate; else - return @(BNCUpdateStatusUpdate); + return BNCUpdateStateUpdate; } - // This is the first Branch install. Check file dates for app install status: - - // Get the install date: - - NSError *error = nil; - NSFileManager *manager = [NSFileManager defaultManager]; - NSURL *libraryURL = - [[manager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] firstObject]; - NSDictionary *attributes = [manager attributesOfItemAtPath:libraryURL.path error:&error]; - NSDate *installDate = [attributes fileCreationDate]; - - // Get the build date: + if (buildDate && [buildDate timeIntervalSince1970] <= 0.0) { + // Invalid buildDate. + buildDate = nil; + } + if (appInstallDate && [appInstallDate timeIntervalSince1970] <= 0.0) { + // Invalid appInstallDate. + appInstallDate = nil; + } - NSString *bundleRoot = [[NSBundle mainBundle] bundlePath]; - NSString *filename = [bundleRoot stringByAppendingPathComponent:@"_CodeSignature"]; - attributes = [manager attributesOfItemAtPath:filename error:&error]; - NSDate *buildDate = [attributes fileModificationDate]; + if (!(buildDate && appInstallDate)) { + return BNCUpdateStateInstall; + } - if ([buildDate compare:installDate] > 0) { - return @(BNCUpdateStatusUpdate); + if ([buildDate compare:appInstallDate] > 0) { + return BNCUpdateStateUpdate; } - if ([installDate timeIntervalSinceNow] > (-60.0 * 60.0 * 24.0)) { - return @(BNCUpdateStatusInstall); + if ([appInstallDate timeIntervalSinceNow] > (-60.0 * 60.0 * 24.0)) { + return BNCUpdateStateInstall; } - return @(BNCUpdateStatusNonUpdate); + return BNCUpdateStateNonUpdate; } ++ (NSDate*) appInstallDate { + // Get the app install date: -+ (NSNumber *)getUpdateState { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + NSURL *libraryURL = + [[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] firstObject]; + NSDictionary *attributes = [fileManager attributesOfItemAtPath:libraryURL.path error:&error]; + if (error) { + NSLog(@"Error getting library date: %@.", error); + return nil; + } + NSDate *installDate = [attributes fileCreationDate]; + if (installDate == nil || [installDate timeIntervalSince1970] <= 0.0) { + NSLog(@"Error: Invalid install date."); + return nil; + } + return installDate; +} - NSString *storedAppVersion = [BNCPreferenceHelper preferenceHelper].appVersion; - NSString *currentAppVersion = [BNCSystemObserver getAppVersion]; - NSFileManager *manager = [NSFileManager defaultManager]; ++ (NSDate*) dateForPathComponent:(NSString*)component inURLs:(NSArray*)fileInfoURLs { + BOOL success = NO; + NSError *error = nil; + NSDate *buildDate = nil; + for (NSURL *fileInfoURL in fileInfoURLs) { + if ([[fileInfoURL lastPathComponent] isEqualToString:component]) { + success = [fileInfoURL getResourceValue:&buildDate + forKey:NSURLCreationDateKey + error:&error]; + break; + } + } + if (!success || error) { + NSLog(@"Can't retrieve attributes. Success: %d Error: %@.", success, error); + return nil; + } + return buildDate; +} - // for creation date - NSURL *documentsDirRoot = [[manager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; - NSDictionary *documentsDirAttributes = [manager attributesOfItemAtPath:documentsDirRoot.path error:nil]; - NSDate *creationDate = [documentsDirAttributes fileCreationDate]; ++ (NSDate*) appBuildDate { + // Get the build date: + + NSFileManager *fileManager = [NSFileManager defaultManager]; - // for modification date NSError *error = nil; NSString *bundleRoot = [[NSBundle mainBundle] bundlePath]; - NSDictionary *bundleAttributes = [manager attributesOfItemAtPath:bundleRoot error:&error]; - NSDate *modificationDate = [bundleAttributes fileModificationDate]; - - if (creationDate == nil || [creationDate timeIntervalSince1970] <= 0.0 || - modificationDate == nil || [modificationDate timeIntervalSince1970] <= 0.0) - return [self getUpdateState_10_2_2]; - - // No stored version - if (!storedAppVersion) { - // Modification and Creation date are more than 24 hours' worth of seconds different indicates - // an update. This would be the case that they were installing a new version of the app that was - // adding Branch for the first time, where we don't already have an NSUserDefaults value. - if (ABS([modificationDate timeIntervalSinceDate:creationDate]) > 86400) { - return @(BNCUpdateStatusUpdate); - } - - // If we don't have one of the previous dates, or they're less than 60 apart, - // we understand this to be an install. - return @(BNCUpdateStatusInstall); + if (!bundleRoot) bundleRoot = @"."; + NSURL *bundleRootURL = [NSURL URLWithString:[@"file://" stringByAppendingString:bundleRoot]]; + if (bundleRootURL == nil) return nil; + NSArray *fileInfoURLs = + [fileManager contentsOfDirectoryAtURL:bundleRootURL + includingPropertiesForKeys:@[ NSURLCreationDateKey ] + options:0 + error:&error]; + if (error) { + NSLog(@"Error retreiving bundle info: %@.", error); + return nil; } - // Have a stored version, but it isn't the same as the current value indicates an update - else if (![storedAppVersion isEqualToString:currentAppVersion]) { - return @(BNCUpdateStatusUpdate); + NSDate *buildDate = nil; + buildDate = [self dateForPathComponent:@"_CodeSignature" inURLs:fileInfoURLs]; + if (!buildDate) { + buildDate = [self dateForPathComponent:@"META-INF" inURLs:fileInfoURLs]; } - - // Otherwise, we have a stored version, and it is equal. - // Not an update, not an install. - return @(BNCUpdateStatusNonUpdate); + if (!buildDate) { + buildDate = [self dateForPathComponent:@"PkgInfo" inURLs:fileInfoURLs]; + } + if (!buildDate) { + buildDate = [self dateForPathComponent:@"xctest" inURLs:fileInfoURLs]; + } + if (buildDate == nil || [buildDate timeIntervalSince1970] <= 0.0) { + NSLog(@"Error: Invalid build date."); + return nil; + } + return buildDate; } + (void)setUpdateState { NSString *currentAppVersion = [BNCSystemObserver getAppVersion]; [BNCPreferenceHelper preferenceHelper].appVersion = currentAppVersion; -} - -+ (NSString *)getOS { - return @"iOS"; -} - -+ (NSString *)getOSVersion { - UIDevice *device = [UIDevice currentDevice]; - return [device systemVersion]; -} - -+ (NSNumber *)getScreenWidth { - UIScreen *mainScreen = [UIScreen mainScreen]; - float scaleFactor = mainScreen.scale; - CGFloat width = mainScreen.bounds.size.width * scaleFactor; - return [NSNumber numberWithInteger:(NSInteger)width]; -} - -+ (NSNumber *)getScreenHeight { - UIScreen *mainScreen = [UIScreen mainScreen]; - float scaleFactor = mainScreen.scale; - CGFloat height = mainScreen.bounds.size.height * scaleFactor; - return [NSNumber numberWithInteger:(NSInteger)height]; + [[BNCPreferenceHelper preferenceHelper] synchronize]; } @end diff --git a/src/ios/dependencies/Branch-SDK/BNCXcode7Support.h b/src/ios/dependencies/Branch-SDK/BNCXcode7Support.h index d85d685e..0c2aca00 100644 --- a/src/ios/dependencies/Branch-SDK/BNCXcode7Support.h +++ b/src/ios/dependencies/Branch-SDK/BNCXcode7Support.h @@ -19,5 +19,7 @@ - (NSString*) languageCode; @end +typedef NSString * UIActivityType; +typedef NSString * UIApplicationOpenURLOptionsKey; #endif diff --git a/src/ios/dependencies/Branch-SDK/Branch.h b/src/ios/dependencies/Branch-SDK/Branch.h index 0ddac3e9..d89f5da8 100644 --- a/src/ios/dependencies/Branch-SDK/Branch.h +++ b/src/ios/dependencies/Branch-SDK/Branch.h @@ -20,6 +20,8 @@ #import "BranchActivityItemProvider.h" #import "BranchDeepLinkingController.h" #import "BNCCommerceEvent.h" +#import "BranchShareLink.h" +#import "BNCXcode7Support.h" /** `Branch` is the primary interface of the Branch iOS SDK. Currently, all interactions you will make are funneled through this class. It is not meant to be instantiated or subclassed, usage should be limited to the global instance. @@ -31,7 +33,7 @@ /// @name Constants ///---------------- -#pragma mark - Branch Link Features +#pragma mark Branch Link Features /** ## Branch Link Features @@ -337,12 +339,45 @@ typedef NS_ENUM(NSUInteger, BranchCreditHistoryOrder) { - (BOOL)handleDeepLink:(NSURL *)url; /** - Allow Branch to handle restoration from an NSUserActivity, returning whether or not it was from a Branch link. + Allow Branch to handle restoration from an NSUserActivity, returning whether or not it was + from a Branch link. @param userActivity The NSUserActivity that caused the app to be opened. */ - (BOOL)continueUserActivity:(NSUserActivity *)userActivity; +/** + Call this method from inside your app delegate's `application:openURL:sourceApplication:annotation:` + method with the so that Branch can open the passed URL. + + @param application The application that was passed to your app delegate. + @param url The URL that was passed to your app delegate. + @param sourceApplication The sourceApplication that was passed to your app delegate. + @param annotation The annotation that was passed to your app delegate. + @return Returns `YES` if Branch handled the passed URL. + */ +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation; + +/** + Call this method from inside your app delegate's `application:openURL:options:` + method with the so that Branch can open the passed URL. + + This method is functionally the same as calling the Branch method + `application:openURL:sourceApplication:annotation:`. This method matches the new Apple appDelegate + method for convenience. + + @param application The application that was passed to your app delegate. + @param url The URL that was passed to your app delegate. + @param options The options dictionary that was passed to your app delegate. + @return Returns `YES` if Branch handled the passed URL. + */ +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options; + ///-------------------------------- /// @name Push Notification Support ///-------------------------------- diff --git a/src/ios/dependencies/Branch-SDK/Branch.m b/src/ios/dependencies/Branch-SDK/Branch.m index 19f361a4..f2835b24 100644 --- a/src/ios/dependencies/Branch-SDK/Branch.m +++ b/src/ios/dependencies/Branch-SDK/Branch.m @@ -424,6 +424,10 @@ -(void)addWhiteListedScheme:(NSString *)scheme { } - (BOOL)handleDeepLink:(NSURL *)url { + return [self handleDeepLink:url fromSelf:NO]; +} + +- (BOOL)handleDeepLink:(NSURL *)url fromSelf:(BOOL)isFromSelf { BOOL handled = NO; if (url && ![url isEqual:[NSNull null]]) { @@ -447,6 +451,9 @@ - (BOOL)handleDeepLink:(NSURL *)url { NSDictionary *params = [BNCEncodingUtils decodeQueryStringToDictionary:query]; if (params[@"link_click_id"]) { handled = YES; + if (isFromSelf) { + [self resetUserSession]; + } self.preferenceHelper.linkClickIdentifier = params[@"link_click_id"]; } } @@ -456,6 +463,33 @@ - (BOOL)handleDeepLink:(NSURL *)url { return handled; } +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + + BOOL fromSelf = NO; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + if (bundleID && sourceApplication) { + fromSelf = [bundleID isEqualToString:sourceApplication]; + } + return [self handleDeepLink:url fromSelf:fromSelf]; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + + NSString *source = nil; + NSString *annotation = nil; + if (UIApplicationOpenURLOptionsSourceApplicationKey && + UIApplicationOpenURLOptionsAnnotationKey) { + source = options[UIApplicationOpenURLOptionsSourceApplicationKey]; + annotation = options[UIApplicationOpenURLOptionsAnnotationKey]; + } + return [self application:application openURL:url sourceApplication:source annotation:annotation]; +} + - (BOOL)continueUserActivity:(NSUserActivity *)userActivity { //check to see if a browser activity needs to be handled if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { @@ -540,61 +574,69 @@ - (void)setAppleSearchAdsDebugMode { } - (BOOL)checkAppleSearchAdsAttribution { - if (self.delayForAppleAds) { - Class ADClientClass = NSClassFromString(@"ADClient"); - SEL sharedClient = NSSelectorFromString(@"sharedClient"); - SEL requestAttribution = NSSelectorFromString(@"requestAttributionDetailsWithBlock:"); - - if (ADClientClass && [ADClientClass instancesRespondToSelector:requestAttribution] && - [ADClientClass methodForSelector:sharedClient]) { - id sharedClientInstance = ((id (*)(id, SEL))[ADClientClass methodForSelector:sharedClient])(ADClientClass, sharedClient); - - self.preferenceHelper.shouldWaitForInit = YES; - self.preferenceHelper.checkedAppleSearchAdAttribution = YES; - self.asyncRequestCount++; + if (!self.delayForAppleAds) + return NO; + + Class ADClientClass = NSClassFromString(@"ADClient"); + SEL sharedClient = NSSelectorFromString(@"sharedClient"); + SEL requestAttribution = NSSelectorFromString(@"requestAttributionDetailsWithBlock:"); + + BOOL ADClientIsAvailable = + ADClientClass && + [ADClientClass instancesRespondToSelector:requestAttribution] && + [ADClientClass methodForSelector:sharedClient]; + + if (!ADClientIsAvailable) { + NSString *warning = @"delayForAppleAds is true but ADClient is not available. Is the iAD.framework included and iOS 10?"; + [[BNCPreferenceHelper preferenceHelper] logWarning:warning]; + return NO; + } + + id sharedClientInstance = ((id (*)(id, SEL))[ADClientClass methodForSelector:sharedClient])(ADClientClass, sharedClient); + + self.preferenceHelper.shouldWaitForInit = YES; + self.preferenceHelper.checkedAppleSearchAdAttribution = YES; + self.asyncRequestCount++; + + void (^__nullable completionBlock)(NSDictionary *attrDetails, NSError *error) = ^void(NSDictionary *__nullable attrDetails, NSError *__nullable error) { + self.asyncRequestCount--; + + if (attrDetails && [attrDetails count]) { + self.preferenceHelper.appleSearchAdDetails = attrDetails; + } + else if (self.searchAdsDebugMode) { + NSMutableDictionary *testInfo = [[NSMutableDictionary alloc] init]; - void (^__nullable completionBlock)(NSDictionary *attrDetails, NSError *error) = ^void(NSDictionary *__nullable attrDetails, NSError *__nullable error) { - self.asyncRequestCount--; - - if (attrDetails && [attrDetails count]) { - self.preferenceHelper.appleSearchAdDetails = attrDetails; - } - else if (self.searchAdsDebugMode) { - NSMutableDictionary *testInfo = [[NSMutableDictionary alloc] init]; - - NSMutableDictionary *testDetails = [[NSMutableDictionary alloc] init]; - [testDetails setObject:[NSNumber numberWithBool:YES] forKey:@"iad-attribution"]; - [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-campaign-id"]; - [testDetails setObject:@"DebugAppleSearchAdsCampaignName" forKey:@"iad-campaign-name"]; - [testDetails setObject:@"2016-09-09T01:33:17Z" forKey:@"iad-click-date"]; - [testDetails setObject:@"2016-09-09T01:33:17Z" forKey:@"iad-conversion-date"]; - [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-creative-id"]; - [testDetails setObject:@"CreativeName" forKey:@"iad-creative-name"]; - [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-lineitem-id"]; - [testDetails setObject:@"LineName" forKey:@"iad-lineitem-name"]; - [testDetails setObject:@"OrgName" forKey:@"iad-org-name"]; - - [testInfo setObject:testDetails forKey:@"Version3.1"]; - - self.preferenceHelper.appleSearchAdDetails = testInfo; - } - - // if there's another async attribution check in flight, don't continue with init - if (self.asyncRequestCount > 0) { return; } - - self.preferenceHelper.shouldWaitForInit = NO; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self initUserSessionAndCallCallback:!self.isInitialized]; - }); - }; + NSMutableDictionary *testDetails = [[NSMutableDictionary alloc] init]; + [testDetails setObject:[NSNumber numberWithBool:YES] forKey:@"iad-attribution"]; + [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-campaign-id"]; + [testDetails setObject:@"DebugAppleSearchAdsCampaignName" forKey:@"iad-campaign-name"]; + [testDetails setObject:@"2016-09-09T01:33:17Z" forKey:@"iad-click-date"]; + [testDetails setObject:@"2016-09-09T01:33:17Z" forKey:@"iad-conversion-date"]; + [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-creative-id"]; + [testDetails setObject:@"CreativeName" forKey:@"iad-creative-name"]; + [testDetails setObject:[NSNumber numberWithInteger:1234567890] forKey:@"iad-lineitem-id"]; + [testDetails setObject:@"LineName" forKey:@"iad-lineitem-name"]; + [testDetails setObject:@"OrgName" forKey:@"iad-org-name"]; - ((void (*)(id, SEL, void (^ __nullable)(NSDictionary *__nullable attrDetails, NSError * __nullable error)))[sharedClientInstance methodForSelector:requestAttribution])(sharedClientInstance, requestAttribution, completionBlock); + [testInfo setObject:testDetails forKey:@"Version3.1"]; - return YES; + self.preferenceHelper.appleSearchAdDetails = testInfo; } - } - return NO; + + // if there's another async attribution check in flight, don't continue with init + if (self.asyncRequestCount > 0) { return; } + + self.preferenceHelper.shouldWaitForInit = NO; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self initUserSessionAndCallCallback:!self.isInitialized]; + }); + }; + + ((void (*)(id, SEL, void (^ __nullable)(NSDictionary *__nullable attrDetails, NSError * __nullable error)))[sharedClientInstance methodForSelector:requestAttribution])(sharedClientInstance, requestAttribution, completionBlock); + + return YES; } diff --git a/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.h b/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.h index 1b453a6d..2eeaf7e2 100644 --- a/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.h +++ b/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.h @@ -32,4 +32,5 @@ - (id)initWithParams:(NSDictionary *)params andTags:(NSArray *)tags andFeature:(NSString *)feature andStage:(NSString *)stage andAlias:(NSString *)alias __attribute__((deprecated(("Use the delegate method instead"))));; - (id)initWithParams:(NSDictionary *)params tags:(NSArray *)tags feature:(NSString *)feature stage:(NSString *)stage campaign:(NSString *)campaign alias:(NSString *)alias delegate:(id )delegate; ++ (NSString *)humanReadableChannelWithActivityType:(NSString *)activityString; @end diff --git a/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.m b/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.m index ebcbea98..5b42e268 100644 --- a/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.m +++ b/src/ios/dependencies/Branch-SDK/BranchActivityItemProvider.m @@ -9,6 +9,7 @@ #import "BranchActivityItemProvider.h" #import "Branch.h" #import "BNCSystemObserver.h" +#import "BNCDeviceInfo.h" @interface BranchActivityItemProvider () @@ -39,7 +40,7 @@ - (id)initWithParams:(NSDictionary *)params tags:(NSArray *)tags feature:(NSStri _stage = stage; _campaign = campaign; _alias = alias; - _userAgentString = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + _userAgentString = [BNCDeviceInfo userAgentString]; _delegate = delegate; } @@ -77,23 +78,23 @@ - (id)item { + (NSString *)humanReadableChannelWithActivityType:(NSString *)activityString { NSString *channel = activityString; //default NSDictionary *channelMappings = [[NSDictionary alloc] initWithObjectsAndKeys: - @"Pasteboard", UIActivityTypeCopyToPasteboard, - @"Email", UIActivityTypeMail, - @"SMS", UIActivityTypeMessage, - @"Facebook", UIActivityTypePostToFacebook, - @"Twitter", UIActivityTypePostToTwitter, - @"Weibo", UIActivityTypePostToWeibo, - @"Reading List", UIActivityTypeAddToReadingList, - @"Airdrop", UIActivityTypeAirDrop, - @"flickr", UIActivityTypePostToFlickr, + @"Pasteboard", UIActivityTypeCopyToPasteboard, + @"Email", UIActivityTypeMail, + @"SMS", UIActivityTypeMessage, + @"Facebook", UIActivityTypePostToFacebook, + @"Twitter", UIActivityTypePostToTwitter, + @"Weibo", UIActivityTypePostToWeibo, + @"Reading List",UIActivityTypeAddToReadingList, + @"Airdrop", UIActivityTypeAirDrop, + @"flickr", UIActivityTypePostToFlickr, @"Tencent Weibo", UIActivityTypePostToTencentWeibo, - @"Vimeo", UIActivityTypePostToVimeo, + @"Vimeo", UIActivityTypePostToVimeo, @"Apple Notes", @"com.apple.mobilenotes.SharingExtension", - @"Slack", @"com.tinyspeck.chatlyio.share", - @"WhatsApp", @"net.whatsapp.WhatsApp.ShareExtension", - @"WeChat", @"com.tencent.xin.sharetimeline", - @"LINE", @"jp.naver.line.Share", - @"Pinterest", @"pinterest.ShareExtension", + @"Slack", @"com.tinyspeck.chatlyio.share", + @"WhatsApp", @"net.whatsapp.WhatsApp.ShareExtension", + @"WeChat", @"com.tencent.xin.sharetimeline", + @"LINE", @"jp.naver.line.Share", + @"Pinterest", @"pinterest.ShareExtension", // Keys for older app versions -- diff --git a/src/ios/dependencies/Branch-SDK/BranchShareLink.h b/src/ios/dependencies/Branch-SDK/BranchShareLink.h new file mode 100644 index 00000000..29977044 --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BranchShareLink.h @@ -0,0 +1,112 @@ +// +// BranchShareLink.h +// Branch-SDK +// +// Created by Edward Smith on 3/13/17. +// Copyright © 2017 Branch Metrics. All rights reserved. +// + +#import +#import "BranchUniversalObject.h" +@class BranchShareLink; + +@protocol BranchShareLinkDelegate +@optional + +/** +This delegate method is called during the course of user interaction while sharing a +Branch link. The linkProperties, such as channel, or the share text parameters can be +altered as appropriate for the particular user-chosen activityType. + +This delegate method will be called multiple times during a share interaction and might be +called on a background thread. + +@param shareLink The calling BranchShareLink that is currently sharing. +*/ +- (void) branchShareLinkWillShare:(BranchShareLink*_Nonnull)shareLink; + +/** +This delegate method is called when sharing has completed. + +@param shareLink The Branch share action sheet that has just completed. +@param completed This parameter is YES if sharing completed successfully and the user did not cancel. +@param error This parameter contains any errors that occurred will attempting to share. +*/ +- (void) branchShareLink:(BranchShareLink*_Nonnull)shareLink + didComplete:(BOOL)completed + withError:(NSError*_Nullable)error; +@end + +#pragma mark - BranchShareLink + +/** +The `BranchShareLink` class facilitates sharing Branch links using a `UIActivityViewController` +user experience. + +The `BranchShareLink` is a new class that is similar to but has more functionality than the old +`[BranchUniversalObject showShareSheetWithLinkProperties:...]` methods. + +The `BranchShareLink` is initialized with the `BranchUniversalObject` and `BranchLinkProperties` +objects that will be used to generate the Branch link. + +After the `BranchShareLink` object is created, set any configuration properties on the activity +sheet object, and then call `showFromViewController:anchor:` to show the activity sheet. + +A delegate on the BranchShareLink can further configure the share experience. For instance the link +parameters can be changed depending on the activity that the user selects. +*/ + +@interface BranchShareLink : NSObject + +/** +Creates a BranchShareLink object. + +@oaram universalObject The Branch Universal Object the will be shared. +@param linkProperties The link properties that the link will have. +*/ +- (instancetype _Nullable) initWithUniversalObject:(BranchUniversalObject*_Nonnull)universalObject + linkProperties:(BranchLinkProperties*_Nonnull)linkProperties; + + +///Returns an array of activity item providers, one for the Branch Universal Object, +///one for the share text (if provided), and one for the shareObject (if provided). +- (NSArray*_Nonnull) activityItems; + +/** +Presents a UIActivityViewController that shares the Branch link. + +@oaram viewController The parent view controller from which to present the the activity sheet. +@param anchor The anchor point for the activity sheet. Used for iPad form factors. +*/ +- (void) presentActivityViewControllerFromViewController:(UIViewController*_Nullable)viewController + anchor:(UIBarButtonItem*_Nullable)anchor; + +///The title for the share sheet. +@property (nonatomic, strong) NSString*_Nullable title; + +///Share text for the item. +///This text can be changed later when the `branchShareSheetWillShare:` delegate method is called. +@property (nonatomic, strong) NSString*_Nullable shareText; + +///An additional, user defined, non-typed, object to be shared. +///This object can be changed later when the `branchShareSheetWillShare:` delegate method is called. +@property (nonatomic, strong) id _Nullable shareObject; + +///The resulting Branch URL that was shared. +@property (nonatomic, strong, readonly) NSURL*_Nullable shareURL; + +///The activity type that the user chose. +@property (nonatomic, strong, readonly) NSString*_Nullable activityType; + +///Extra server parameters that should be included with the link data. +@property (nonatomic, strong) NSMutableDictionary*_Nullable serverParameters; + +///The Branch Universal Object that will be shared. +@property (nonatomic, strong, readonly) BranchUniversalObject*_Nonnull universalObject; + +///The link properties for the created URL. +@property (nonatomic, strong, readonly) BranchLinkProperties*_Nonnull linkProperties; + +///The delegate. See 'BranchShareLinkDelegate' above for a description. +@property (nonatomic, weak) id_Nullable delegate; +@end diff --git a/src/ios/dependencies/Branch-SDK/BranchShareLink.m b/src/ios/dependencies/Branch-SDK/BranchShareLink.m new file mode 100644 index 00000000..d76bcdfb --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/BranchShareLink.m @@ -0,0 +1,258 @@ +// +// BranchShareLink.m +// Branch-SDK +// +// Created by Edward Smith on 3/13/17. +// Copyright © 2017 Branch Metrics. All rights reserved. +// + +#import "BranchShareLink.h" +#import "BranchConstants.h" +#import "BNCFabricAnswers.h" +#import "BranchActivityItemProvider.h" +#import "BNCDeviceInfo.h" +#import "BNCXcode7Support.h" +@class BranchShareActivityItem; + +typedef NS_ENUM(NSInteger, BranchShareActivityItemType) { + BranchShareActivityItemTypeBranchURL = 0, + BranchShareActivityItemTypeShareText, + BranchShareActivityItemTypeOther, +}; + +#pragma mark BranchShareLink + +@interface BranchShareLink () { + NSPointerArray* _activityItems; +} + +- (id) shareObjectForItem:(BranchShareActivityItem*)activityItem + activityType:(UIActivityType)activityType; + +@property (nonatomic, strong) NSURL *shareURL; +@end + +#pragma mark - BranchShareActivityItem + +@interface BranchShareActivityItem : UIActivityItemProvider +@property (nonatomic, assign) BranchShareActivityItemType itemType; +@property (nonatomic, strong) BranchShareLink *parent; +@end + +@implementation BranchShareActivityItem + +- (id) initWithPlaceholderItem:(id)placeholderItem { + self = [super initWithPlaceholderItem:placeholderItem]; + if (!self) return self; + + if ([placeholderItem isKindOfClass:NSString.class]) { + self.itemType = BranchShareActivityItemTypeShareText; + } else { + self.itemType = BranchShareActivityItemTypeOther; + } + + return self; +} + +- (id) item { + return [self.parent shareObjectForItem:self activityType:self.activityType]; +} + +@end + +#pragma mark - BranchShareLink + +@implementation BranchShareLink + +- (instancetype _Nullable) initWithUniversalObject:(BranchUniversalObject*_Nonnull)universalObject + linkProperties:(BranchLinkProperties*_Nonnull)linkProperties { + self = [super init]; + if (!self) return self; + + _universalObject = universalObject; + _linkProperties = linkProperties; + return self; +} + +- (void) shareDidComplete:(BOOL)completed activityError:(NSError*)error { + if ([self.delegate respondsToSelector:@selector(branchShareLink:didComplete:withError:)]) { + [self.delegate branchShareLink:self didComplete:completed withError:error]; + } + [self.universalObject userCompletedAction:BNCShareCompletedEvent]; + NSDictionary *attributes = [self.universalObject getDictionaryWithCompleteLinkProperties:self.linkProperties]; + [BNCFabricAnswers sendEventWithName:@"Branch Share" andAttributes:attributes]; +} + +- (NSArray*_Nonnull) activityItems { + if (_activityItems) { + return [_activityItems allObjects]; + } + + // Make sure we can share + + if (!(self.universalObject.canonicalIdentifier || + self.universalObject.canonicalUrl || + self.universalObject.title)) { + NSLog(@"Warning: A canonicalIdentifier, canonicalURL, or title are required to uniquely" + " identify content. In order to not break the end user experience with sharing," + " Branch SDK will proceed to create a URL, but content analytics may not properly" + " include this URL."); + } + + self.serverParameters = + [[self.universalObject getParamsForServerRequestWithAddedLinkProperties:self.linkProperties] + mutableCopy]; + if (self.linkProperties.matchDuration) { + self.serverParameters[BRANCH_REQUEST_KEY_URL_DURATION] = @(self.linkProperties.matchDuration); + } + + // Log share initiated event + [self.universalObject userCompletedAction:BNCShareInitiatedEvent]; + + NSMutableArray *items = [NSMutableArray new]; + BranchShareActivityItem *item = nil; + if (self.shareText.length) { + item = [[BranchShareActivityItem alloc] initWithPlaceholderItem:self.shareText]; + item.itemType = BranchShareActivityItemTypeShareText; + item.parent = self; + [items addObject:item]; + } + + NSString *URLString = + [[Branch getInstance] + getLongURLWithParams:self.serverParameters + andChannel:self.linkProperties.channel + andTags:self.linkProperties.tags + andFeature:self.linkProperties.feature + andStage:self.linkProperties.stage + andAlias:self.linkProperties.alias]; + NSURL *URL = [[NSURL alloc] initWithString:URLString]; + item = [[BranchShareActivityItem alloc] initWithPlaceholderItem:URL]; + item.itemType = BranchShareActivityItemTypeBranchURL; + item.parent = self; + [items addObject:item]; + + [_activityItems addPointer:(__bridge void * _Nullable)(item)]; + + if (self.shareObject) { + item = [[BranchShareActivityItem alloc] initWithPlaceholderItem:self.shareObject]; + item.itemType = BranchShareActivityItemTypeOther; + item.parent = self; + [items addObject:item]; + } + + _activityItems = [NSPointerArray weakObjectsPointerArray]; + for (item in items) + [_activityItems addPointer:(__bridge void * _Nullable)(item)]; + + return items; +} + +- (void) presentActivityViewControllerFromViewController:(UIViewController*_Nullable)viewController + anchor:(UIBarButtonItem*_Nullable)anchor { + + UIActivityViewController *shareViewController = + [[UIActivityViewController alloc] + initWithActivityItems:self.activityItems + applicationActivities:nil]; + shareViewController.title = self.title; + + if ([shareViewController respondsToSelector:@selector(completionWithItemsHandler)]) { + shareViewController.completionWithItemsHandler = + ^ (NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + [self shareDidComplete:completed activityError:activityError]; + }; + } else { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + shareViewController.completionHandler = + ^ (UIActivityType activityType, BOOL completed) { + [self shareDidComplete:completed activityError:nil]; + }; + #pragma clang diagnostic pop + } + + if (self.linkProperties.controlParams[BRANCH_LINK_DATA_KEY_EMAIL_SUBJECT]) { + @try { + [shareViewController + setValue:self.linkProperties.controlParams[BRANCH_LINK_DATA_KEY_EMAIL_SUBJECT] + forKey:@"subject"]; + } + @catch (NSException *exception) { + NSLog(@"Warning: Unable to setValue 'emailSubject' forKey 'subject' on UIActivityViewController."); + } + } + + UIViewController *presentingViewController = nil; + if ([viewController respondsToSelector:@selector(presentViewController:animated:completion:)]) { + presentingViewController = viewController; + } else { + Class UIApplicationClass = NSClassFromString(@"UIApplication"); + UIViewController *rootController = [UIApplicationClass sharedApplication].delegate.window.rootViewController; + if ([rootController respondsToSelector:@selector(presentViewController:animated:completion:)]) { + presentingViewController = rootController; + } + } + + if (!presentingViewController) { + NSLog(@"[Branch] Error: No view controller is present to show the share sheet. Aborting."); + return; + } + + // Required for iPad/Universal apps on iOS 8+ + if ([presentingViewController respondsToSelector:@selector(popoverPresentationController)]) { + shareViewController.popoverPresentationController.sourceView = presentingViewController.view; + if (anchor) { + shareViewController.popoverPresentationController.barButtonItem = anchor; + } + } + [presentingViewController presentViewController:shareViewController animated:YES completion:nil]; +} + +- (id) shareObjectForItem:(BranchShareActivityItem*)activityItem + activityType:(UIActivityType)activityType { + + _activityType = [activityType copy]; + self.linkProperties.channel = + [BranchActivityItemProvider humanReadableChannelWithActivityType:self.activityType]; + + if ([self.delegate respondsToSelector:@selector(branchShareLinkWillShare:)]) { + [self.delegate branchShareLinkWillShare:self]; + } + if (activityItem.itemType == BranchShareActivityItemTypeShareText) { + return self.shareText; + } + if (activityItem.itemType == BranchShareActivityItemTypeOther) { + return self.shareObject; + } + + // Else activityItem.itemType == BranchShareActivityItemTypeURL + + // Because Facebook et al immediately scrape URLs, we add an additional parameter to the + // existing list, telling the backend to ignore the first click. + + NSDictionary *scrapers = @{ + @"Facebook": @1, + @"Twitter": @1, + @"Slack": @1, + @"Apple Notes": @1 + }; + NSString *userAgentString = nil; + if (self.linkProperties.channel && scrapers[self.linkProperties.channel]) { + userAgentString = [BNCDeviceInfo userAgentString]; + } + NSString *URLString = + [[Branch getInstance] + getShortURLWithParams:self.serverParameters + andTags:self.linkProperties.tags + andChannel:self.linkProperties.channel + andFeature:self.linkProperties.feature + andStage:self.linkProperties.stage + andCampaign:self.linkProperties.campaign + andAlias:self.linkProperties.alias + ignoreUAString:userAgentString + forceLinkCreation:YES]; + return [NSURL URLWithString:URLString]; +} + +@end diff --git a/src/ios/dependencies/Branch-SDK/BranchUniversalObject.h b/src/ios/dependencies/Branch-SDK/BranchUniversalObject.h index 7b2c6a3f..24c7676f 100644 --- a/src/ios/dependencies/Branch-SDK/BranchUniversalObject.h +++ b/src/ios/dependencies/Branch-SDK/BranchUniversalObject.h @@ -47,6 +47,7 @@ typedef NS_ENUM(NSInteger, ContentIndexMode) { - (void)registerViewWithCallback:(nullable callbackWithParams)callback; - (void)userCompletedAction:(nonnull NSString *)action; +- (void)userCompletedAction:(nonnull NSString *)action withState:(nullable NSDictionary *)state; - (nullable NSString *)getShortUrlWithLinkProperties:(nonnull BranchLinkProperties *)linkProperties; - (nullable NSString *)getShortUrlWithLinkPropertiesAndIgnoreFirstClick:(nonnull BranchLinkProperties *)linkProperties; @@ -72,4 +73,6 @@ typedef NS_ENUM(NSInteger, ContentIndexMode) { - (nonnull NSString *)description; +- (NSDictionary*_Nonnull)getDictionaryWithCompleteLinkProperties:(BranchLinkProperties*_Nonnull)linkProperties; +- (NSDictionary*_Nonnull)getParamsForServerRequestWithAddedLinkProperties:(BranchLinkProperties*_Nonnull)linkProperties; @end diff --git a/src/ios/dependencies/Branch-SDK/BranchUniversalObject.m b/src/ios/dependencies/Branch-SDK/BranchUniversalObject.m index eccc08fe..2e45d758 100644 --- a/src/ios/dependencies/Branch-SDK/BranchUniversalObject.m +++ b/src/ios/dependencies/Branch-SDK/BranchUniversalObject.m @@ -11,6 +11,7 @@ #import "BranchConstants.h" #import "BNCPreferenceHelper.h" #import "BNCFabricAnswers.h" +#import "BNCDeviceInfo.h" @implementation BranchUniversalObject { BNCPreferenceHelper *_preferenceHelper; @@ -71,18 +72,82 @@ - (void)registerViewWithCallback:(callbackWithParams)callback { [[Branch getInstance] registerViewWithParams:[self getParamsForServerRequest] andCallback:callback]; } -- (void)userCompletedAction:(NSString *)action { +- (void)userCompletedAction:(NSString *)action +{ + [self userCompletedAction:action withState:nil]; +} + +- (void)userCompletedAction:(NSString *)action withState:(NSDictionary *)state { NSMutableDictionary *actionPayload = [[NSMutableDictionary alloc] init]; NSDictionary *linkParams = [self getParamsForServerRequest]; if (self.canonicalIdentifier && linkParams) { actionPayload[BNCCanonicalIdList] = @[self.canonicalIdentifier]; actionPayload[self.canonicalIdentifier] = linkParams; + + if (state) { + // Add in user params + [actionPayload addEntriesFromDictionary:state]; + } + [[Branch getInstance] userCompletedAction:action withState:actionPayload]; if (self.automaticallyListOnSpotlight && [action isEqualToString:BNCRegisterViewEvent]) [self listOnSpotlight]; } } ++ (BranchUniversalObject *)getBranchUniversalObjectFromDictionary:(NSDictionary *)dictionary { + BranchUniversalObject *universalObject = [[BranchUniversalObject alloc] init]; + + // Build BranchUniversalObject base properties + universalObject.metadata = [dictionary copy]; + if (dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_IDENTIFIER]) { + universalObject.canonicalIdentifier = dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_IDENTIFIER]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_URL]) { + universalObject.canonicalUrl = dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_URL]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_OG_TITLE]) { + universalObject.title = dictionary[BRANCH_LINK_DATA_KEY_OG_TITLE]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_OG_DESCRIPTION]) { + universalObject.contentDescription = dictionary[BRANCH_LINK_DATA_KEY_OG_DESCRIPTION]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_OG_IMAGE_URL]) { + universalObject.imageUrl = dictionary[BRANCH_LINK_DATA_KEY_OG_IMAGE_URL]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_PUBLICLY_INDEXABLE]) { + if (dictionary[BRANCH_LINK_DATA_KEY_PUBLICLY_INDEXABLE] == 0) { + universalObject.contentIndexMode = ContentIndexModePrivate; + } + else { + universalObject.contentIndexMode = ContentIndexModePublic; + } + } + + if (dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE] && [dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE] isKindOfClass:[NSNumber class]]) { + NSNumber *millisecondsSince1970 = dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE]; + universalObject.expirationDate = [NSDate dateWithTimeIntervalSince1970:millisecondsSince1970.integerValue/1000]; + } + if (dictionary[BRANCH_LINK_DATA_KEY_KEYWORDS]) { + universalObject.keywords = dictionary[BRANCH_LINK_DATA_KEY_KEYWORDS]; + } + if (dictionary[BNCPurchaseAmount]) { + universalObject.price = [dictionary[BNCPurchaseAmount] floatValue]; + } + if (dictionary[BNCPurchaseCurrency]) { + universalObject.currency = dictionary[BNCPurchaseCurrency]; + } + + if (dictionary[BRANCH_LINK_DATA_KEY_CONTENT_TYPE]) { + universalObject.type = dictionary[BRANCH_LINK_DATA_KEY_CONTENT_TYPE]; + } + return universalObject; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"BranchUniversalObject \n canonicalIdentifier: %@ \n title: %@ \n contentDescription: %@ \n imageUrl: %@ \n metadata: %@ \n type: %@ \n contentIndexMode: %ld \n keywords: %@ \n expirationDate: %@", self.canonicalIdentifier, self.title, self.contentDescription, self.imageUrl, self.metadata, self.type, (long)self.contentIndexMode, self.keywords, self.expirationDate]; +} + #pragma mark - Link Creation Methods - (NSString *)getShortUrlWithLinkProperties:(BranchLinkProperties *)linkProperties { @@ -129,7 +194,7 @@ - (NSString *)getShortUrlWithLinkPropertiesAndIgnoreFirstClick:(BranchLinkProper return nil; } // keep this operation outside of sync operation below. - NSString *UAString = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + NSString *UAString = [BNCDeviceInfo userAgentString]; return [[Branch getInstance] getShortURLWithParams:[self getParamsForServerRequestWithAddedLinkProperties:linkProperties] andTags:linkProperties.tags @@ -142,6 +207,8 @@ - (NSString *)getShortUrlWithLinkPropertiesAndIgnoreFirstClick:(BranchLinkProper forceLinkCreation:YES]; } +#pragma mark - Share Sheets + - (UIActivityItemProvider *)getBranchActivityItemWithLinkProperties:(BranchLinkProperties *)linkProperties { if (!self.canonicalIdentifier && !self.canonicalUrl && !self.title) { [_preferenceHelper logWarning:@"A canonicalIdentifier, canonicalURL, or title are required to uniquely identify content. In order to not break the end user experience with sharing, Branch SDK will proceed to create a URL, but content analytics may not properly include this URL."]; @@ -241,6 +308,8 @@ - (void)showShareSheetWithLinkProperties:(BranchLinkProperties *)linkProperties } } +#pragma mark - Spotlight + - (void)listOnSpotlight { [self listOnSpotlightWithCallback:nil]; } @@ -303,59 +372,6 @@ - (void)listOnSpotlightWithIdentifierCallback:(callbackWithUrlAndSpotlightIdenti spotlightCallback:spotlightCallback]; } -+ (BranchUniversalObject *)getBranchUniversalObjectFromDictionary:(NSDictionary *)dictionary { - BranchUniversalObject *universalObject = [[BranchUniversalObject alloc] init]; - - // Build BranchUniversalObject base properties - universalObject.metadata = [dictionary copy]; - if (dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_IDENTIFIER]) { - universalObject.canonicalIdentifier = dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_IDENTIFIER]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_URL]) { - universalObject.canonicalUrl = dictionary[BRANCH_LINK_DATA_KEY_CANONICAL_URL]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_OG_TITLE]) { - universalObject.title = dictionary[BRANCH_LINK_DATA_KEY_OG_TITLE]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_OG_DESCRIPTION]) { - universalObject.contentDescription = dictionary[BRANCH_LINK_DATA_KEY_OG_DESCRIPTION]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_OG_IMAGE_URL]) { - universalObject.imageUrl = dictionary[BRANCH_LINK_DATA_KEY_OG_IMAGE_URL]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_PUBLICLY_INDEXABLE]) { - if (dictionary[BRANCH_LINK_DATA_KEY_PUBLICLY_INDEXABLE] == 0) { - universalObject.contentIndexMode = ContentIndexModePrivate; - } - else { - universalObject.contentIndexMode = ContentIndexModePublic; - } - } - - if (dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE] && [dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE] isKindOfClass:[NSNumber class]]) { - NSNumber *millisecondsSince1970 = dictionary[BRANCH_LINK_DATA_KEY_CONTENT_EXPIRATION_DATE]; - universalObject.expirationDate = [NSDate dateWithTimeIntervalSince1970:millisecondsSince1970.integerValue/1000]; - } - if (dictionary[BRANCH_LINK_DATA_KEY_KEYWORDS]) { - universalObject.keywords = dictionary[BRANCH_LINK_DATA_KEY_KEYWORDS]; - } - if (dictionary[BNCPurchaseAmount]) { - universalObject.price = [dictionary[BNCPurchaseAmount] floatValue]; - } - if (dictionary[BNCPurchaseCurrency]) { - universalObject.currency = dictionary[BNCPurchaseCurrency]; - } - - if (dictionary[BRANCH_LINK_DATA_KEY_CONTENT_TYPE]) { - universalObject.type = dictionary[BRANCH_LINK_DATA_KEY_CONTENT_TYPE]; - } - return universalObject; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"BranchUniversalObject \n canonicalIdentifier: %@ \n title: %@ \n contentDescription: %@ \n imageUrl: %@ \n metadata: %@ \n type: %@ \n contentIndexMode: %ld \n keywords: %@ \n expirationDate: %@", self.canonicalIdentifier, self.title, self.contentDescription, self.imageUrl, self.metadata, self.type, (long)self.contentIndexMode, self.keywords, self.expirationDate]; -} - #pragma mark - Private methods - (NSDictionary *)getParamsForServerRequest { diff --git a/src/ios/dependencies/Branch-SDK/NSString+Branch.h b/src/ios/dependencies/Branch-SDK/NSString+Branch.h new file mode 100644 index 00000000..07a8e5e7 --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/NSString+Branch.h @@ -0,0 +1,30 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// NSString+Branch.h +// Branch.framework +// +// NSString Additions +// Edward Smith, February 2017 +// +// -©- Copyright © 2017 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +#import + + +@interface NSString (Branch) + +///@discussion Compares the receiver to a masked string. Masked characters (the '*' character) are +/// ignored for purposes of the compare. +/// +///@return YES if string (ignoring any masked characters) is equal to the receiver. +- (BOOL) bnc_isEqualToMaskedString:(NSString*_Nullable)string; + +///@return Returns a string that is truncated at the first null character. +- (NSString*_Nonnull) bnc_stringTruncatedAtNull; + +@end diff --git a/src/ios/dependencies/Branch-SDK/NSString+Branch.m b/src/ios/dependencies/Branch-SDK/NSString+Branch.m new file mode 100644 index 00000000..f32aa231 --- /dev/null +++ b/src/ios/dependencies/Branch-SDK/NSString+Branch.m @@ -0,0 +1,47 @@ + + +//-------------------------------------------------------------------------------------------------- +// +// NSString+Branch.m +// Branch.framework +// +// NSString Additions +// Edward Smith, February 2017 +// +// -©- Copyright © 2017 Branch, all rights reserved. -©- +// +//-------------------------------------------------------------------------------------------------- + + +#import "NSString+Branch.h" + + +void BNCForceNSStringCategoryToLoad() __attribute__((constructor)); +void BNCForceNSStringCategoryToLoad() { + // Nothing here, but forces linker to load the category. +} + + +@implementation NSString (Branch) + +- (BOOL) bnc_isEqualToMaskedString:(NSString*_Nullable)string { + if (!string) return NO; + if (self.length != string.length) return NO; + for (NSInteger idx = 0; idx < self.length; idx++) { + unichar p = [self characterAtIndex:idx]; + unichar q = [string characterAtIndex:idx]; + if (q != '*' && p != q) return NO; + } + return YES; +} + +- (NSString*_Nonnull) bnc_stringTruncatedAtNull { + NSRange range = [self rangeOfString:@"\0"]; + if (range.location == NSNotFound) + return self; + range.length = range.location; + range.location = 0; + return [self substringWithRange:range]; +} + +@end diff --git a/src/ios/dependencies/Branch-SDK/Requests/BranchLoadRewardsRequest.m b/src/ios/dependencies/Branch-SDK/Requests/BranchLoadRewardsRequest.m index a17e81d4..4f283d0e 100644 --- a/src/ios/dependencies/Branch-SDK/Requests/BranchLoadRewardsRequest.m +++ b/src/ios/dependencies/Branch-SDK/Requests/BranchLoadRewardsRequest.m @@ -42,6 +42,7 @@ - (void)processResponse:(BNCServerResponse *)response error:(NSError *)error { BOOL hasUpdated = NO; BNCPreferenceHelper *preferenceHelper = [BNCPreferenceHelper preferenceHelper]; + [preferenceHelper synchronize]; NSDictionary *currentCreditDictionary = [preferenceHelper getCreditDictionary]; NSArray *responseKeys = [response.data allKeys]; NSArray *storedKeys = [currentCreditDictionary allKeys]; @@ -78,6 +79,7 @@ - (void)processResponse:(BNCServerResponse *)response error:(NSError *)error { } } + [preferenceHelper synchronize]; if (self.callback) { self.callback(hasUpdated, nil); } diff --git a/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlRequest.m b/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlRequest.m index db17abf1..ad53df97 100644 --- a/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlRequest.m +++ b/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlRequest.m @@ -56,7 +56,7 @@ - (void)makeRequest:(BNCServerInterface *)serverInterface key:(NSString *)key ca BNCPreferenceHelper *preferenceHelper = [BNCPreferenceHelper preferenceHelper]; params[BRANCH_REQUEST_KEY_DEVICE_FINGERPRINT_ID] = preferenceHelper.deviceFingerprintID; - if (!_isSpotlightRequest) { + if (!_isSpotlightRequest && _alias.length == 0) { params[BRANCH_REQUEST_KEY_BRANCH_IDENTITY] = preferenceHelper.identityID; } params[BRANCH_REQUEST_KEY_SESSION_ID] = preferenceHelper.sessionID; diff --git a/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlSyncRequest.m b/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlSyncRequest.m index 94bca133..76fcf461 100644 --- a/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlSyncRequest.m +++ b/src/ios/dependencies/Branch-SDK/Requests/BranchShortUrlSyncRequest.m @@ -53,7 +53,8 @@ - (BNCServerResponse *)makeRequest:(BNCServerInterface *)serverInterface key:(NS BNCPreferenceHelper *preferenceHelper = [BNCPreferenceHelper preferenceHelper]; params[BRANCH_REQUEST_KEY_DEVICE_FINGERPRINT_ID] = preferenceHelper.deviceFingerprintID; - params[BRANCH_REQUEST_KEY_BRANCH_IDENTITY] = preferenceHelper.identityID; + if (_alias.length == 0) + params[BRANCH_REQUEST_KEY_BRANCH_IDENTITY] = preferenceHelper.identityID; params[BRANCH_REQUEST_KEY_SESSION_ID] = preferenceHelper.sessionID; return [serverInterface postRequest:params @@ -64,6 +65,8 @@ - (BNCServerResponse *)makeRequest:(BNCServerInterface *)serverInterface key:(NS - (NSString *)processResponse:(BNCServerResponse *)response { if (![response.statusCode isEqualToNumber:@200]) { + NSLog(@"Short link creation received HTTP status code %@.", response.statusCode); + NSLog(@"Warning: Using long link instead."); NSString *failedUrl = nil; NSString *userUrl = [BNCPreferenceHelper preferenceHelper].userUrl; if (userUrl) {