diff --git a/Makefile b/Makefile index b56cc71fb4a..95e2d4a4a1f 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,9 @@ post-install: @cp ./native_modules/RNCWebViewManager.java node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @cp ./native_modules/RNCWKWebView.m node_modules/react-native-webview/ios/RNCWKWebView.m + # Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set + @cp ./native_modules/RNCookieManagerIOS.m node_modules/react-native-cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m + @rm -f node_modules/intl/.babelrc @# Hack to get react-intl and its dependencies to work with react-native @# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js diff --git a/native_modules/RNCookieManagerIOS.m b/native_modules/RNCookieManagerIOS.m new file mode 100644 index 00000000000..92732f4fe5e --- /dev/null +++ b/native_modules/RNCookieManagerIOS.m @@ -0,0 +1,274 @@ +#import "RNCookieManagerIOS.h" +#if __has_include("RCTConvert.h") +#import "RCTConvert.h" +#else +#import +#endif + +static NSString * const NOT_AVAILABLE_ERROR_MESSAGE = @"WebKit/WebKit-Components are only available with iOS11 and higher!"; + +@implementation RNCookieManagerIOS + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.formatter = [NSDateFormatter new]; + [self.formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; + } + return self; +} + ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD( + set:(NSDictionary *)props + useWebKit:(BOOL)useWebKit + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + NSString *name = [RCTConvert NSString:props[@"name"]]; + NSString *value = [RCTConvert NSString:props[@"value"]]; + NSString *domain = [RCTConvert NSString:props[@"domain"]]; + NSString *origin = [RCTConvert NSString:props[@"origin"]]; + NSString *path = [RCTConvert NSString:props[@"path"]]; + NSString *version = [RCTConvert NSString:props[@"version"]]; + NSDate *expiration = [RCTConvert NSDate:props[@"expiration"]]; + + NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary]; + [cookieProperties setObject:name forKey:NSHTTPCookieName]; + [cookieProperties setObject:value forKey:NSHTTPCookieValue]; + [cookieProperties setObject:domain forKey:NSHTTPCookieDomain]; + [cookieProperties setObject:origin forKey:NSHTTPCookieOriginURL]; + [cookieProperties setObject:path forKey:NSHTTPCookiePath]; + [cookieProperties setObject:version forKey:NSHTTPCookieVersion]; + [cookieProperties setObject:expiration forKey:NSHTTPCookieExpires]; + + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties]; + + NSLog(@"SETTING COOKIE"); + NSLog(@"%@", cookie); + + if (useWebKit) { + if (@available(iOS 11.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^(){ + WKHTTPCookieStore *cookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + [cookieStore setCookie:cookie completionHandler:nil]; + resolve(nil); + }); + } else { + reject(@"", NOT_AVAILABLE_ERROR_MESSAGE, nil); + } + } else { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + resolve(nil); + } +} + +RCT_EXPORT_METHOD(setFromResponse:(NSURL *)url + value:(NSString *)value + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:@{@"Set-Cookie": value} forURL:url]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:NULL]; + resolve(nil); +} + +RCT_EXPORT_METHOD(getFromResponse:(NSURL *)url + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:response.URL]; + NSMutableDictionary *dics = [NSMutableDictionary dictionary]; + + for (int i = 0; i < cookies.count; i++) { + NSHTTPCookie *cookie = [cookies objectAtIndex:i]; + [dics setObject:cookie.value forKey:cookie.name]; + NSLog(@"cookie: name=%@, value=%@", cookie.name, cookie.value); + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + } + resolve(dics); + }]; +} + +-(NSString *)getDomainName:(NSURL *) url +{ + NSString *separator = @"."; + NSInteger maxLength = 2; + + NSURLComponents *components = [[NSURLComponents alloc]initWithURL:url resolvingAgainstBaseURL:FALSE]; + NSArray *separatedHost = [components.host componentsSeparatedByString:separator]; + NSInteger count = [separatedHost count]; + NSInteger endPosition = count; + NSInteger startPosition = count - maxLength; + + NSMutableString *result = [[NSMutableString alloc]init]; + for (NSUInteger i = startPosition; i != endPosition; i++) { + [result appendString:separator]; + [result appendString:[separatedHost objectAtIndex:i]]; + } + return result; +} + +RCT_EXPORT_METHOD( + get:(NSURL *) url + useWebKit:(BOOL)useWebKit + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + if (useWebKit) { + if (@available(iOS 11.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^(){ + NSString *topLevelDomain = [self getDomainName:url]; + + WKHTTPCookieStore *cookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + [cookieStore getAllCookies:^(NSArray *allCookies) { + NSMutableDictionary *cookies = [NSMutableDictionary dictionary]; + for(NSHTTPCookie *currentCookie in allCookies) { + if([currentCookie.domain containsString:topLevelDomain]) { + [cookies setObject:currentCookie.value forKey:currentCookie.name]; + } + } + resolve(cookies); + }]; + }); + } else { + reject(@"", NOT_AVAILABLE_ERROR_MESSAGE, nil); + } + } else { + NSMutableDictionary *cookies = [NSMutableDictionary dictionary]; + for (NSHTTPCookie *c in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]) { + NSMutableDictionary *d = [NSMutableDictionary dictionary]; + [d setObject:c.value forKey:@"value"]; + [d setObject:c.name forKey:@"name"]; + [d setObject:c.domain forKey:@"domain"]; + [d setObject:c.path forKey:@"path"]; + NSString *expires = [self.formatter stringFromDate:c.expiresDate]; + if (expires != nil) { + [d setObject:expires forKey:@"expiresDate"]; + } + [cookies setObject:d forKey:c.name]; + } + resolve(cookies); + } +} + +RCT_EXPORT_METHOD( + clearAll:(BOOL)useWebKit + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + if (useWebKit) { + if (@available(iOS 11.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^(){ + WKHTTPCookieStore *cookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + [cookieStore getAllCookies:^(NSArray *allCookies) { + for(NSHTTPCookie *currentCookie in allCookies) { + // Uses the NSHTTPCookie directly has no effect, nor deleted the cookie nor thrown an error. + // Create a new cookie with the given values and delete this one do the work. + NSMutableDictionary *cookieData = [NSMutableDictionary dictionary]; + [cookieData setValue:currentCookie.name forKey:NSHTTPCookieName]; + [cookieData setValue:currentCookie.value forKey:NSHTTPCookieValue]; + [cookieData setValue:currentCookie.domain forKey:NSHTTPCookieDomain]; + [cookieData setValue:currentCookie.path forKey:NSHTTPCookiePath]; + + NSHTTPCookie *newCookie = [NSHTTPCookie cookieWithProperties:cookieData]; + [cookieStore deleteCookie:newCookie completionHandler:^{}]; + } + resolve(nil); + }]; + }); + } else { + reject(@"", NOT_AVAILABLE_ERROR_MESSAGE, nil); + } + } else { + NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + for (NSHTTPCookie *c in cookieStorage.cookies) { + [cookieStorage deleteCookie:c]; + } + resolve(nil); + } +} + +RCT_EXPORT_METHOD(clearByName:(NSString *) name + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + for (NSHTTPCookie *c in cookieStorage.cookies) { + if ([[c name] isEqualToString:name]) { + [cookieStorage deleteCookie:c]; + } + } + resolve(nil); +} + +RCT_EXPORT_METHOD( + getAll:(BOOL)useWebKit + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + if (useWebKit) { + if (@available(iOS 11.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^(){ + WKHTTPCookieStore *cookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + [cookieStore getAllCookies:^(NSArray *allCookies) { + resolve([self createCookieList: allCookies]); + }]; + }); + } else { + reject(@"", NOT_AVAILABLE_ERROR_MESSAGE, nil); + } + } else { + NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + resolve([self createCookieList:cookieStorage.cookies]); + } +} + +RCT_EXPORT_METHOD(getAll:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + NSMutableDictionary *cookies = [NSMutableDictionary dictionary]; + for (NSHTTPCookie *c in cookieStorage.cookies) { + NSMutableDictionary *d = [NSMutableDictionary dictionary]; + [d setObject:c.value forKey:@"value"]; + [d setObject:c.name forKey:@"name"]; + [d setObject:c.domain forKey:@"domain"]; + [d setObject:c.path forKey:@"path"]; + NSString *expires = [self.formatter stringFromDate:c.expiresDate]; + if (expires != nil) { + [d setObject:expires forKey:@"expiresDate"]; + } + [cookies setObject:d forKey:c.name]; + } +} + +-(NSDictionary *)createCookieList:(NSArray*)cookies +{ + NSMutableDictionary *cookieList = [NSMutableDictionary dictionary]; + for (NSHTTPCookie *cookie in cookies) { + // NSLog(@"COOKIE: %@", cookie); + [cookieList setObject:[self createCookieData:cookie] forKey:cookie.name]; + } + return cookieList; +} + +-(NSDictionary *)createCookieData:(NSHTTPCookie *)cookie +{ + NSMutableDictionary *cookieData = [NSMutableDictionary dictionary]; + [cookieData setObject:cookie.value forKey:@"value"]; + [cookieData setObject:cookie.name forKey:@"name"]; + [cookieData setObject:cookie.domain forKey:@"domain"]; + [cookieData setObject:cookie.path forKey:@"path"]; + return cookieData; +} + +@end