mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:03 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			1998 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			1998 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXNetworkObserver.m
 | |
| //  Derived from:
 | |
| //
 | |
| //  PDAFNetworkDomainController.m
 | |
| //  PonyDebugger
 | |
| //
 | |
| //  Created by Mike Lewis on 2/27/12.
 | |
| //
 | |
| //  Licensed to Square, Inc. under one or more contributor license agreements.
 | |
| //  See the LICENSE file distributed with this work for the terms under
 | |
| //  which Square, Inc. licenses this file to you.
 | |
| //
 | |
| //  Heavily modified and added to by Tanner Bennett and various other contributors.
 | |
| //  git blame details these modifications.
 | |
| //
 | |
| 
 | |
| #import "FLEXNetworkObserver.h"
 | |
| #import "FLEXNetworkRecorder.h"
 | |
| #import "FLEXUtility.h"
 | |
| #import "NSUserDefaults+FLEX.h"
 | |
| #import "NSObject+FLEX_Reflection.h"
 | |
| #import "FLEXMethod.h"
 | |
| #import "Firestore.h"
 | |
| 
 | |
| #import <objc/runtime.h>
 | |
| #import <objc/message.h>
 | |
| #import <dispatch/queue.h>
 | |
| #include <dlfcn.h>
 | |
| 
 | |
| NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
 | |
| 
 | |
| typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
 | |
| typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(NSURLSession *, id, NSURLSessionAsyncCompletion);
 | |
| 
 | |
| @interface FLEXInternalRequestState : NSObject
 | |
| 
 | |
| @property (nonatomic, copy) NSURLRequest *request;
 | |
| @property (nonatomic) NSMutableData *dataAccumulator;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation FLEXInternalRequestState
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface FLEXNetworkObserver (NSURLConnectionHelpers)
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response delegate:(id<NSURLConnectionDelegate>)delegate;
 | |
| - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id<NSURLConnectionDelegate>)delegate;
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id<NSURLConnectionDelegate>)delegate;
 | |
| 
 | |
| - (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id<NSURLConnectionDelegate>)delegate;
 | |
| - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id<NSURLConnectionDelegate>)delegate;
 | |
| 
 | |
| - (void)connectionWillCancel:(NSURLConnection *)connection;
 | |
| 
 | |
| @end
 | |
| 
 | |
| 
 | |
| @interface FLEXNetworkObserver (NSURLSessionTaskHelpers)
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 | |
| didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| - (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id<NSURLSessionDelegate>)delegate;
 | |
| 
 | |
| - (void)URLSessionTaskWillResume:(NSURLSessionTask *)task;
 | |
| 
 | |
| - (void)websocketTask:(NSURLSessionWebSocketTask *)task
 | |
|         sendMessagage:(NSURLSessionWebSocketMessage *)message API_AVAILABLE(ios(13.0));
 | |
| - (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
 | |
|                                      error:(NSError *)error API_AVAILABLE(ios(13.0));
 | |
| 
 | |
| - (void)websocketTask:(NSURLSessionWebSocketTask *)task
 | |
|      receiveMessagage:(NSURLSessionWebSocketMessage *)message
 | |
|                 error:(NSError *)error API_AVAILABLE(ios(13.0));
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface FLEXNetworkObserver ()
 | |
| 
 | |
| @property (nonatomic) NSMutableDictionary<NSString *, FLEXInternalRequestState *> *requestStatesForRequestIDs;
 | |
| @property (nonatomic) dispatch_queue_t queue;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation FLEXNetworkObserver
 | |
| 
 | |
| #pragma mark - Public Methods
 | |
| 
 | |
| + (void)setEnabled:(BOOL)enabled {
 | |
|     BOOL previouslyEnabled = [self isEnabled];
 | |
|     
 | |
|     NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled = enabled;
 | |
|     
 | |
|     if (enabled) {
 | |
|         // Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times.
 | |
|         // By doing the injection lazily, we keep the impact of the tool lower when this feature isn't enabled.
 | |
|         [self setNetworkMonitorHooks];
 | |
|     }
 | |
|     
 | |
|     if (previouslyEnabled != enabled) {
 | |
|         [NSNotificationCenter.defaultCenter postNotificationName:kFLEXNetworkObserverEnabledStateChangedNotification object:self];
 | |
|     }
 | |
| }
 | |
| 
 | |
| + (BOOL)isEnabled {
 | |
|     return NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled;
 | |
| }
 | |
| 
 | |
| + (void)load {
 | |
|     // We don't want to do the swizzling from +load because not all the
 | |
|     // delegate classes we want to hook may be loaded at this point.
 | |
|     // However, Firebase classes will definitely be loaded by now,
 | |
|     // so we can definitely hook those sooner if need be.
 | |
|     dispatch_async(dispatch_get_main_queue(), ^{
 | |
|         if ([self isEnabled]) {
 | |
|             [self setNetworkMonitorHooks];
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| #pragma mark - Statics
 | |
| 
 | |
| + (instancetype)sharedObserver {
 | |
|     static FLEXNetworkObserver *sharedObserver = nil;
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         sharedObserver = [self new];
 | |
|     });
 | |
|     return sharedObserver;
 | |
| }
 | |
| 
 | |
| + (NSString *)nextRequestID {
 | |
|     return NSUUID.UUID.UUIDString;
 | |
| }
 | |
| 
 | |
| #pragma mark Delegate Injection Convenience Methods
 | |
| 
 | |
| /// All swizzled delegate methods should make use of this guard.
 | |
| /// This will prevent duplicated sniffing when the original implementation calls up to a superclass
 | |
| /// implementation which we've also swizzled. The superclass implementation (and implementations in
 | |
| /// classes above that) will be executed without interference if called from the original implementation.
 | |
| + (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector
 | |
|                            sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock {
 | |
|     // If we don't have an object to detect nested calls on, just run the original implementation and bail.
 | |
|     // This case can happen if someone besides the URL loading system calls the delegate methods directly.
 | |
|     // See https://github.com/Flipboard/FLEX/issues/61 for an example.
 | |
|     if (!object) {
 | |
|         originalImplementationBlock();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     const void *key = selector;
 | |
| 
 | |
|     // Don't run the sniffing block if we're inside a nested call
 | |
|     if (!objc_getAssociatedObject(object, key)) {
 | |
|         sniffingBlock();
 | |
|     }
 | |
| 
 | |
|     // Mark that we're calling through to the original so we can detect nested calls
 | |
|     objc_setAssociatedObject(object, key, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 | |
|     originalImplementationBlock();
 | |
|     objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 | |
| }
 | |
| 
 | |
| #pragma mark - Hooking
 | |
| 
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$)(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
 | |
| static void (*_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$)(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
 | |
| static void _logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
 | |
| 
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$)(
 | |
|  _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, BOOL, void (^)(NSError *));
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$)(
 | |
|  _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, NSArray *, void (^)(NSError *));
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$)(
 | |
|  _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, void (^)(NSError *));
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$)(
 | |
|  _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, void (^)(NSError *));
 | |
| 
 | |
| static void _logos_register_hook(Class _class, SEL _cmd, IMP _new, IMP *_old) {
 | |
|     unsigned int _count, _i;
 | |
|     Class _searchedClass = _class;
 | |
|     Method *_methods;
 | |
|     while (_searchedClass) {
 | |
|         _methods = class_copyMethodList(_searchedClass, &_count);
 | |
|         for (_i = 0; _i < _count; _i++) {
 | |
|             if (method_getName(_methods[_i]) == _cmd) {
 | |
|                 if (_class == _searchedClass) {
 | |
|                     *_old = method_getImplementation(_methods[_i]);
 | |
|                     *_old = class_replaceMethod(_class, _cmd, _new, method_getTypeEncoding(_methods[_i]));
 | |
|                 } else {
 | |
|                     class_addMethod(_class, _cmd, _new, method_getTypeEncoding(_methods[_i]));
 | |
|                 }
 | |
|                 free(_methods);
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         free(_methods);
 | |
|         _searchedClass = class_getSuperclass(_searchedClass);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static Class _logos_superclass$_ungrouped$FIRDocumentReference;
 | |
| static void (*_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$)(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
 | |
| static Class _logos_superclass$_ungrouped$FIRQuery;
 | |
| static void (*_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$)(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
 | |
| static Class _logos_superclass$_ungrouped$FIRCollectionReference;
 | |
| static FIRDocumentReference * (*_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$)(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRCollectionReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, void (^)(NSError *error));
 | |
| 
 | |
| #pragma mark Firebase, Reading Data
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST self, SEL _cmd, FIRDocumentSnapshotBlock completion) {
 | |
|     
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder recordFIRDocumentWillFetch:self withTransactionID:requestID];
 | |
|     // Hook callback
 | |
|     FIRDocumentSnapshotBlock orig = completion;
 | |
|     completion = ^(FIRDocumentSnapshot *document, NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDocumentDidFetch:document error:error transactionID:requestID];
 | |
|         orig(document, error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$ ? _logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(getDocumentWithCompletion:)))(self, _cmd, completion);
 | |
| }
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST self, SEL _cmd, FIRQuerySnapshotBlock completion) {
 | |
|     
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder recordFIRQueryWillFetch:self withTransactionID:requestID];
 | |
|     // Hook callback
 | |
|     FIRQuerySnapshotBlock orig = completion;
 | |
|     completion = ^(FIRQuerySnapshot *query, NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRQueryDidFetch:query error:error transactionID:requestID];
 | |
|         orig(query, error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$ ? _logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRQuery, @selector(getDocumentsWithCompletion:)))(self, _cmd, completion);
 | |
| }
 | |
| 
 | |
| #pragma mark Firebase, Writing Data
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$setData$merge$completion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
 | |
|     SEL __unused _cmd, NSDictionary<NSString *, id> * documentData, BOOL merge, void (^completion)(NSError *)) {
 | |
| 
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder
 | |
|         recordFIRWillSetData:self
 | |
|         data:documentData
 | |
|         merge:@(merge)
 | |
|         mergeFields:nil
 | |
|         transactionID:requestID
 | |
|     ];
 | |
|     
 | |
|     // Hook callback
 | |
|     void (^orig)(NSError *) = completion;
 | |
|     completion = ^(NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDidSetData:error transactionID:requestID];
 | |
|         orig(error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(setData:merge:completion:)))(self, _cmd, documentData, merge, completion);
 | |
| }
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
 | |
|     SEL __unused _cmd, NSDictionary<NSString *, id> * documentData,
 | |
|     NSArray * mergeFields, void (^completion)(NSError *)) {
 | |
| 
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder
 | |
|         recordFIRWillSetData:self
 | |
|         data:documentData
 | |
|         merge:nil
 | |
|         mergeFields:mergeFields
 | |
|         transactionID:requestID
 | |
|     ];
 | |
| 
 | |
|     // Hook callback
 | |
|     void (^orig)(NSError *) = completion;
 | |
|     completion = ^(NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDidSetData:error transactionID:requestID];
 | |
|         orig(error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(setData:mergeFields:completion:)))(self, _cmd, documentData, mergeFields, completion);
 | |
| }
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$updateData$completion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
 | |
|     SEL __unused _cmd, NSDictionary<id, id> * fields, void (^completion)(NSError *)) {
 | |
| 
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder recordFIRWillUpdateData:self fields:fields transactionID:requestID];
 | |
|     // Hook callback
 | |
|     void (^orig)(NSError *) = completion;
 | |
|     completion = ^(NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDidUpdateData:error transactionID:requestID];
 | |
|         orig(error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(updateData:completion:)))(self, _cmd, fields, completion);
 | |
| }
 | |
| 
 | |
| static void _logos_method$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
 | |
|     SEL __unused _cmd, void (^completion)(NSError *)) {
 | |
| 
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
|     
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder recordFIRWillDeleteDocument:self transactionID:requestID];
 | |
|     // Hook callback
 | |
|     void (^orig)(NSError *) = completion;
 | |
|     completion = ^(NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDidDeleteDocument:error transactionID:requestID];
 | |
|         orig(error);
 | |
|     };
 | |
|     
 | |
|     // Forward invocation
 | |
|     (_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$ ? _logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(deleteDocumentWithCompletion:)))(self, _cmd, completion);
 | |
| }
 | |
| 
 | |
| static FIRDocumentReference * _logos_method$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$(
 | |
|     _LOGOS_SELF_TYPE_NORMAL FIRCollectionReference * _LOGOS_SELF_CONST __unused self,
 | |
|     SEL __unused _cmd, NSDictionary<NSString *, id> * data, void (^completion)(NSError *error)) {
 | |
| 
 | |
|     // Generate transaction ID
 | |
|     NSString *requestID = [FLEXNetworkObserver nextRequestID];
 | |
| 
 | |
|     // Hook callback
 | |
|     void (^orig)(NSError *) = completion;
 | |
|     completion = ^(NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordFIRDidAddDocument:error transactionID:requestID];
 | |
|         orig(error);
 | |
|     };
 | |
| 
 | |
|     // Forward invocation
 | |
|     FIRDocumentReference *ret = (_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$ ? _logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRCollectionReference, @selector(addDocumentWithData:completion:)))(self, _cmd, data, completion);
 | |
| 
 | |
|     // Record transaction start
 | |
|     [FLEXNetworkRecorder.defaultRecorder recordFIRWillAddDocument:self document:ret transactionID:requestID];
 | |
| 
 | |
|     // Return
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| + (void)setNetworkMonitorHooks {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         [self hookFirebaseThings];
 | |
|         [self injectIntoAllNSURLThings];
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)hookFirebaseThings {
 | |
|     Class _logos_class$_ungrouped$FIRDocumentReference = objc_getClass("FIRDocumentReference");
 | |
|     _logos_superclass$_ungrouped$FIRDocumentReference = class_getSuperclass(_logos_class$_ungrouped$FIRDocumentReference);
 | |
|     Class _logos_class$_ungrouped$FIRQuery = objc_getClass("FIRQuery");
 | |
|     _logos_superclass$_ungrouped$FIRQuery = class_getSuperclass(_logos_class$_ungrouped$FIRQuery);
 | |
|     Class _logos_class$_ungrouped$FIRCollectionReference = objc_getClass("FIRCollectionReference");
 | |
|     _logos_superclass$_ungrouped$FIRCollectionReference = class_getSuperclass(_logos_class$_ungrouped$FIRCollectionReference);
 | |
| 
 | |
|     // Reading //
 | |
| 
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRDocumentReference,
 | |
|         @selector(getDocumentWithCompletion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$
 | |
|     );
 | |
| 
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRQuery,
 | |
|         @selector(getDocumentsWithCompletion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$
 | |
|     );
 | |
| 
 | |
|     // Writing //
 | |
| 
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRDocumentReference,
 | |
|         @selector(setData:merge:completion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRDocumentReference$setData$merge$completion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$
 | |
|     );
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRDocumentReference,
 | |
|         @selector(setData:mergeFields:completion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$
 | |
|     );
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRDocumentReference,
 | |
|         @selector(updateData:completion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRDocumentReference$updateData$completion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$
 | |
|     );
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRDocumentReference,
 | |
|         @selector(deleteDocumentWithCompletion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$
 | |
|     );
 | |
|     _logos_register_hook(
 | |
|         _logos_class$_ungrouped$FIRCollectionReference,
 | |
|         @selector(addDocumentWithData:completion:),
 | |
|         (IMP)&_logos_method$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$,
 | |
|         (IMP *)&_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$
 | |
|     );
 | |
| }
 | |
| 
 | |
| + (void)injectIntoAllNSURLThings {
 | |
|     // Only allow swizzling once.
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         // Swizzle any classes that implement one of these selectors.
 | |
|         const SEL selectors[] = {
 | |
|             @selector(connectionDidFinishLoading:),
 | |
|             @selector(connection:willSendRequest:redirectResponse:),
 | |
|             @selector(connection:didReceiveResponse:),
 | |
|             @selector(connection:didReceiveData:),
 | |
|             @selector(connection:didFailWithError:),
 | |
|             @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:),
 | |
|             @selector(URLSession:dataTask:didReceiveData:),
 | |
|             @selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
 | |
|             @selector(URLSession:task:didCompleteWithError:),
 | |
|             @selector(URLSession:dataTask:didBecomeDownloadTask:),
 | |
|             @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
 | |
|             @selector(URLSession:downloadTask:didFinishDownloadingToURL:)
 | |
|         };
 | |
| 
 | |
|         const int numSelectors = sizeof(selectors) / sizeof(SEL);
 | |
| 
 | |
|         Class *classes = NULL;
 | |
|         int numClasses = objc_getClassList(NULL, 0);
 | |
| 
 | |
|         if (numClasses > 0) {
 | |
|             classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
 | |
|             numClasses = objc_getClassList(classes, numClasses);
 | |
|             for (NSInteger classIndex = 0; classIndex < numClasses; ++classIndex) {
 | |
|                 Class class = classes[classIndex];
 | |
| 
 | |
|                 if (class == [FLEXNetworkObserver class]) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 // Use the C API rather than NSObject methods to avoid sending messages
 | |
|                 // to classes we're not interested in swizzling, which could result
 | |
|                 // in us calling +initialize on potentially uninitialized classes.
 | |
|                 // NOTE: calling class_getInstanceMethod() DOES send +initialize
 | |
|                 // to the class. That's why we iterate through the method list.
 | |
|                 unsigned int methodCount = 0;
 | |
|                 Method *methods = class_copyMethodList(class, &methodCount);
 | |
|                 BOOL matchingSelectorFound = NO;
 | |
|                 for (unsigned int methodIndex = 0; methodIndex < methodCount; methodIndex++) {
 | |
|                     for (int selectorIndex = 0; selectorIndex < numSelectors; ++selectorIndex) {
 | |
|                         if (method_getName(methods[methodIndex]) == selectors[selectorIndex]) {
 | |
|                             [self injectIntoDelegateClass:class];
 | |
|                             matchingSelectorFound = YES;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                     if (matchingSelectorFound) {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 
 | |
|                 free(methods);
 | |
|             }
 | |
|             
 | |
|             free(classes);
 | |
|         }
 | |
| 
 | |
|         [self injectIntoNSURLConnectionCancel];
 | |
|         [self injectIntoNSURLSessionTaskResume];
 | |
| 
 | |
|         [self injectIntoNSURLConnectionAsynchronousClassMethod];
 | |
|         [self injectIntoNSURLConnectionSynchronousClassMethod];
 | |
| 
 | |
|         [self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods];
 | |
|         [self injectIntoNSURLSessionAsyncUploadTaskMethods];
 | |
|         
 | |
|         if (@available(iOS 13.0, *)) {
 | |
|             Class websocketTask = NSClassFromString(@"__NSURLSessionWebSocketTask");
 | |
|             [self injectWebsocketSendMessage:websocketTask];
 | |
|             [self injectWebsocketReceiveMessage:websocketTask];
 | |
|             websocketTask = [NSURLSessionWebSocketTask class];
 | |
|             [self injectWebsocketSendMessage:websocketTask];
 | |
|             [self injectWebsocketReceiveMessage:websocketTask];
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)injectIntoDelegateClass:(Class)cls {
 | |
|     // Connections
 | |
|     [self injectWillSendRequestIntoDelegateClass:cls];
 | |
|     [self injectDidReceiveDataIntoDelegateClass:cls];
 | |
|     [self injectDidReceiveResponseIntoDelegateClass:cls];
 | |
|     [self injectDidFinishLoadingIntoDelegateClass:cls];
 | |
|     [self injectDidFailWithErrorIntoDelegateClass:cls];
 | |
|     
 | |
|     // Sessions
 | |
|     [self injectTaskWillPerformHTTPRedirectionIntoDelegateClass:cls];
 | |
|     [self injectTaskDidReceiveDataIntoDelegateClass:cls];
 | |
|     [self injectTaskDidReceiveResponseIntoDelegateClass:cls];
 | |
|     [self injectTaskDidCompleteWithErrorIntoDelegateClass:cls];
 | |
|     [self injectRespondsToSelectorIntoDelegateClass:cls];
 | |
| 
 | |
|     // Data tasks
 | |
|     [self injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:cls];
 | |
| 
 | |
|     // Download tasks
 | |
|     [self injectDownloadTaskDidWriteDataIntoDelegateClass:cls];
 | |
|     [self injectDownloadTaskDidFinishDownloadingIntoDelegateClass:cls];
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLConnectionCancel {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         Class class = [NSURLConnection class];
 | |
|         SEL selector = @selector(cancel);
 | |
|         SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|         Method originalCancel = class_getInstanceMethod(class, selector);
 | |
| 
 | |
|         void (^swizzleBlock)(NSURLConnection *) = ^(NSURLConnection *slf) {
 | |
|             [FLEXNetworkObserver.sharedObserver connectionWillCancel:slf];
 | |
|             ((void(*)(id, SEL))objc_msgSend)(
 | |
|                 slf, swizzledSelector
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         IMP implementation = imp_implementationWithBlock(swizzleBlock);
 | |
|         class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalCancel));
 | |
|         Method newCancel = class_getInstanceMethod(class, swizzledSelector);
 | |
|         method_exchangeImplementations(originalCancel, newCancel);
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLSessionTaskResume {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         // In iOS 7 resume lives in __NSCFLocalSessionTask
 | |
|         // In iOS 8 resume lives in NSURLSessionTask
 | |
|         // In iOS 9 resume lives in __NSCFURLSessionTask
 | |
|         // In iOS 14 resume lives in NSURLSessionTask
 | |
|         Class baseResumeClass = Nil;
 | |
|         if (![NSProcessInfo.processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
 | |
|             // iOS ... 7
 | |
|             baseResumeClass = NSClassFromString(@"__NSCFLocalSessionTask");
 | |
|         } else {
 | |
|             NSInteger majorVersion = NSProcessInfo.processInfo.operatingSystemVersion.majorVersion;
 | |
|             if (majorVersion < 9 || majorVersion >= 14) {
 | |
|                 // iOS 8 or iOS 14+
 | |
|                 baseResumeClass = [NSURLSessionTask class];
 | |
|             } else {
 | |
|                 // iOS 9 ... 13
 | |
|                 baseResumeClass = NSClassFromString(@"__NSCFURLSessionTask");
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Hook the base implementation of -resume
 | |
|         IMP originalResume = [baseResumeClass instanceMethodForSelector:@selector(resume)];
 | |
|         [self swizzleResumeSelector:@selector(resume) forClass:baseResumeClass];
 | |
|         
 | |
|         // *Sigh*
 | |
|         //
 | |
|         // So, multiple versions of AFNetworking 2.5.X swizzle -resume in various and
 | |
|         // short-sighted ways. If you look through the version history from 2.5.0 upwards,
 | |
|         // you'll see a variety of techniques were tried, including taking a private
 | |
|         // subclass of NSURLSessionTask and calling class_addMethod with `originalResume`
 | |
|         // below, so that a duplicate implementation of -resume exists in that class.
 | |
|         //
 | |
|         // This technique in particular is troublesome, because the implementation in
 | |
|         // `baseResumeClass` is never called at all, which means our swizzle is never invoked.
 | |
|         //
 | |
|         // The only solution is a brute-force one: we must loop over the class tree
 | |
|         // below `baseResumeClass` and check for all classes that implement `af_resume`.
 | |
|         // if the IMP corresponding to that method is equal to `originalResume` then we
 | |
|         // swizzle that in addition to swizzling `resume` on `baseResumeClass` above.
 | |
|         //
 | |
|         // However, we only go to the trouble at all if NSSelectorFromString
 | |
|         // can even find an `"af_resume"` selector in the first place.
 | |
|         SEL sel_af_resume = NSSelectorFromString(@"af_resume");
 | |
|         if (sel_af_resume) {
 | |
|             NSMutableArray<Class> *classTree = FLEXGetAllSubclasses(baseResumeClass, NO).mutableCopy;
 | |
|             for (NSInteger i = 0; i < classTree.count; i++) {
 | |
|                 [classTree addObjectsFromArray:FLEXGetAllSubclasses(classTree[i], NO)];
 | |
|             }
 | |
|             
 | |
|             for (Class current in classTree) {
 | |
|                 IMP af_resume = [current instanceMethodForSelector:sel_af_resume];
 | |
|                 if (af_resume == originalResume) {
 | |
|                     [self swizzleResumeSelector:sel_af_resume forClass:current];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)swizzleResumeSelector:(SEL)selector forClass:(Class)class {
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     Method originalResume = class_getInstanceMethod(class, selector);
 | |
|     IMP implementation = imp_implementationWithBlock(^(NSURLSessionTask *slf) {
 | |
|         
 | |
|         // iOS's internal HTTP parser finalization code is mysteriously not thread safe,
 | |
|         // invoking it asynchronously has a chance to cause a `double free` crash.
 | |
|         // This line below will ask for HTTPBody synchronously, make the HTTPParser
 | |
|         // parse the request, and cache them in advance. After that the HTTPParser
 | |
|         // will be finalized. Make sure other threads inspecting the request
 | |
|         // won't trigger a race to finalize the parser.
 | |
|         [slf.currentRequest HTTPBody];
 | |
| 
 | |
|         [FLEXNetworkObserver.sharedObserver URLSessionTaskWillResume:slf];
 | |
|         ((void(*)(id, SEL))objc_msgSend)(
 | |
|             slf, swizzledSelector
 | |
|         );
 | |
|     });
 | |
|     
 | |
|     class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalResume));
 | |
|     Method newResume = class_getInstanceMethod(class, swizzledSelector);
 | |
|     method_exchangeImplementations(originalResume, newResume);
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLConnectionAsynchronousClassMethod {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
 | |
|         SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:);
 | |
|         SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|         typedef void (^AsyncCompletion)(
 | |
|             NSURLResponse *response, NSData *data, NSError *error
 | |
|         );
 | |
|         typedef void (^SendAsyncRequestBlock)(
 | |
|             Class, NSURLRequest *, NSOperationQueue *, AsyncCompletion
 | |
|         );
 | |
|         SendAsyncRequestBlock swizzleBlock = ^(Class slf,
 | |
|                                                NSURLRequest *request,
 | |
|                                                NSOperationQueue *queue,
 | |
|                                                AsyncCompletion completion) {
 | |
|             if (FLEXNetworkObserver.isEnabled) {
 | |
|                 NSString *requestID = [self nextRequestID];
 | |
|                 [FLEXNetworkRecorder.defaultRecorder
 | |
|                      recordRequestWillBeSentWithRequestID:requestID
 | |
|                      request:request
 | |
|                      redirectResponse:nil
 | |
|                 ];
 | |
|                 
 | |
|                 NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
 | |
|                 [FLEXNetworkRecorder.defaultRecorder recordMechanism:mechanism forRequestID:requestID];
 | |
|                 
 | |
|                 AsyncCompletion wrapper = ^(NSURLResponse *response, NSData *data, NSError *error) {
 | |
|                     [FLEXNetworkRecorder.defaultRecorder
 | |
|                         recordResponseReceivedWithRequestID:requestID
 | |
|                         response:response
 | |
|                     ];
 | |
|                     [FLEXNetworkRecorder.defaultRecorder
 | |
|                          recordDataReceivedWithRequestID:requestID
 | |
|                          dataLength:data.length
 | |
|                     ];
 | |
|                     if (error) {
 | |
|                         [FLEXNetworkRecorder.defaultRecorder
 | |
|                             recordLoadingFailedWithRequestID:requestID
 | |
|                             error:error
 | |
|                         ];
 | |
|                     } else {
 | |
|                         [FLEXNetworkRecorder.defaultRecorder
 | |
|                             recordLoadingFinishedWithRequestID:requestID
 | |
|                             responseBody:data
 | |
|                         ];
 | |
|                     }
 | |
| 
 | |
|                     // Call through to the original completion handler
 | |
|                     if (completion) {
 | |
|                         completion(response, data, error);
 | |
|                     }
 | |
|                 };
 | |
|                 ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                     slf, swizzledSelector, request, queue, wrapper
 | |
|                 );
 | |
|             } else {
 | |
|                 ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                     slf, swizzledSelector, request, queue, completion
 | |
|                 );
 | |
|             }
 | |
|         };
 | |
|         
 | |
|         [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|             onClass:class withBlock:swizzleBlock swizzledSelector:swizzledSelector
 | |
|         ];
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLConnectionSynchronousClassMethod {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
 | |
|         SEL selector = @selector(sendSynchronousRequest:returningResponse:error:);
 | |
|         SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|         typedef NSData * (^AsyncCompletion)(Class, NSURLRequest *, NSURLResponse **, NSError **);
 | |
|         AsyncCompletion swizzleBlock = ^NSData *(Class slf,
 | |
|                                                  NSURLRequest *request,
 | |
|                                                  NSURLResponse **response,
 | |
|                                                  NSError **error) {
 | |
|             NSData *data = nil;
 | |
|             if (FLEXNetworkObserver.isEnabled) {
 | |
|                 NSString *requestID = [self nextRequestID];
 | |
|                 [FLEXNetworkRecorder.defaultRecorder
 | |
|                     recordRequestWillBeSentWithRequestID:requestID
 | |
|                     request:request
 | |
|                     redirectResponse:nil
 | |
|                 ];
 | |
|                 
 | |
|                 NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
 | |
|                 [FLEXNetworkRecorder.defaultRecorder recordMechanism:mechanism forRequestID:requestID];
 | |
|                 NSError *temporaryError = nil;
 | |
|                 NSURLResponse *temporaryResponse = nil;
 | |
|                 data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(
 | |
|                     slf, swizzledSelector, request, &temporaryResponse, &temporaryError
 | |
|                 );
 | |
|                 
 | |
|                 [FLEXNetworkRecorder.defaultRecorder
 | |
|                     recordResponseReceivedWithRequestID:requestID
 | |
|                     response:temporaryResponse
 | |
|                 ];
 | |
|                 [FLEXNetworkRecorder.defaultRecorder
 | |
|                     recordDataReceivedWithRequestID:requestID
 | |
|                     dataLength:data.length
 | |
|                 ];
 | |
|                 
 | |
|                 if (temporaryError) {
 | |
|                     [FLEXNetworkRecorder.defaultRecorder
 | |
|                         recordLoadingFailedWithRequestID:requestID
 | |
|                         error:temporaryError
 | |
|                     ];
 | |
|                 } else {
 | |
|                     [FLEXNetworkRecorder.defaultRecorder
 | |
|                         recordLoadingFinishedWithRequestID:requestID
 | |
|                         responseBody:data
 | |
|                     ];
 | |
|                 }
 | |
|                 
 | |
|                 if (error) {
 | |
|                     *error = temporaryError;
 | |
|                 }
 | |
|                 if (response) {
 | |
|                     *response = temporaryResponse;
 | |
|                 }
 | |
|             } else {
 | |
|                 data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(
 | |
|                     slf, swizzledSelector, request, response, error
 | |
|                 );
 | |
|             }
 | |
| 
 | |
|             return data;
 | |
|         };
 | |
|         
 | |
|         [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|             onClass:class withBlock:swizzleBlock swizzledSelector:swizzledSelector
 | |
|         ];
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         Class class = [NSURLSession class];
 | |
| 
 | |
|         // The method signatures here are close enough that
 | |
|         // we can use the same logic to inject into all of them.
 | |
|         const SEL selectors[] = {
 | |
|             @selector(dataTaskWithRequest:completionHandler:),
 | |
|             @selector(dataTaskWithURL:completionHandler:),
 | |
|             @selector(downloadTaskWithRequest:completionHandler:),
 | |
|             @selector(downloadTaskWithResumeData:completionHandler:),
 | |
|             @selector(downloadTaskWithURL:completionHandler:)
 | |
|         };
 | |
| 
 | |
|         const int numSelectors = sizeof(selectors) / sizeof(SEL);
 | |
| 
 | |
|         for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
 | |
|             SEL selector = selectors[selectorIndex];
 | |
|             SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|             if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:class]) {
 | |
|                 // iOS 7 does not implement these methods on NSURLSession. We actually want to
 | |
|                 // swizzle __NSCFURLSession, which we can get from the class of the shared session
 | |
|                 class = [NSURLSession.sharedSession class];
 | |
|             }
 | |
|             
 | |
|             typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(
 | |
|                 NSURLSession *, id, NSURLSessionAsyncCompletion
 | |
|             );
 | |
|             NSURLSessionNewTaskMethod swizzleBlock = ^NSURLSessionTask *(NSURLSession *slf,
 | |
|                                                                          id argument,
 | |
|                                                                          NSURLSessionAsyncCompletion completion) {
 | |
|                 NSURLSessionTask *task = nil;
 | |
|                 // Check if network observing is on and a callback was provided
 | |
|                 if (FLEXNetworkObserver.isEnabled && completion) {
 | |
|                     NSString *requestID = [self nextRequestID];
 | |
|                     NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
 | |
|                     // "Hook" the completion block
 | |
|                     NSURLSessionAsyncCompletion completionWrapper = [self
 | |
|                         asyncCompletionWrapperForRequestID:requestID
 | |
|                         mechanism:mechanism
 | |
|                         completion:completion
 | |
|                     ];
 | |
|                     
 | |
|                     // Call the original method
 | |
|                     task = ((id(*)(id, SEL, id, id))objc_msgSend)(
 | |
|                         slf, swizzledSelector, argument, completionWrapper
 | |
|                     );
 | |
|                     [self setRequestID:requestID forConnectionOrTask:task];
 | |
|                 } else {
 | |
|                     // Network observer disabled or no callback provided,
 | |
|                     // just pass through to the original method
 | |
|                     task = ((id(*)(id, SEL, id, id))objc_msgSend)(
 | |
|                         slf, swizzledSelector, argument, completion
 | |
|                     );
 | |
|                 }
 | |
|                 return task;
 | |
|             };
 | |
|             
 | |
|             // Actually swizzle
 | |
|             [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|                 onClass:class withBlock:swizzleBlock swizzledSelector:swizzledSelector
 | |
|             ];
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (void)injectIntoNSURLSessionAsyncUploadTaskMethods {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         Class class = [NSURLSession class];
 | |
| 
 | |
|         // The method signatures here are close enough that we can use the same logic to inject into both of them.
 | |
|         // Note that they have 3 arguments, so we can't easily combine with the data and download method above.
 | |
|         typedef NSURLSessionUploadTask *(^UploadTaskMethod)(
 | |
|             NSURLSession *, NSURLRequest *, id, NSURLSessionAsyncCompletion
 | |
|         );
 | |
|         const SEL selectors[] = {
 | |
|             @selector(uploadTaskWithRequest:fromData:completionHandler:),
 | |
|             @selector(uploadTaskWithRequest:fromFile:completionHandler:)
 | |
|         };
 | |
| 
 | |
|         const int numSelectors = sizeof(selectors) / sizeof(SEL);
 | |
| 
 | |
|         for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
 | |
|             SEL selector = selectors[selectorIndex];
 | |
|             SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|             if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:class]) {
 | |
|                 // iOS 7 does not implement these methods on NSURLSession. We actually want to
 | |
|                 // swizzle __NSCFURLSession, which we can get from the class of the shared session
 | |
|                 class = [NSURLSession.sharedSession class];
 | |
|             }
 | |
| 
 | |
|             
 | |
|             UploadTaskMethod swizzleBlock = ^NSURLSessionUploadTask *(NSURLSession * slf,
 | |
|                                                                       NSURLRequest *request,
 | |
|                                                                       id argument,
 | |
|                                                                       NSURLSessionAsyncCompletion completion) {
 | |
|                 NSURLSessionUploadTask *task = nil;
 | |
|                 if (FLEXNetworkObserver.isEnabled && completion) {
 | |
|                     NSString *requestID = [self nextRequestID];
 | |
|                     NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
 | |
|                     NSURLSessionAsyncCompletion completionWrapper = [self
 | |
|                         asyncCompletionWrapperForRequestID:requestID
 | |
|                         mechanism:mechanism
 | |
|                         completion:completion
 | |
|                     ];
 | |
|                     
 | |
|                     task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                         slf, swizzledSelector, request, argument, completionWrapper
 | |
|                     );
 | |
|                     [self setRequestID:requestID forConnectionOrTask:task];
 | |
|                 } else {
 | |
|                     task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                         slf, swizzledSelector, request, argument, completion
 | |
|                     );
 | |
|                 }
 | |
|                 return task;
 | |
|             };
 | |
|             
 | |
|             [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|                 onClass:class withBlock:swizzleBlock swizzledSelector:swizzledSelector
 | |
|             ];
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| + (NSString *)mechanismFromClassMethod:(SEL)selector onClass:(Class)class {
 | |
|     return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
 | |
| }
 | |
| 
 | |
| + (NSURLSessionAsyncCompletion)asyncCompletionWrapperForRequestID:(NSString *)requestID
 | |
|                                                         mechanism:(NSString *)mechanism
 | |
|                                                        completion:(NSURLSessionAsyncCompletion)completion {
 | |
|     NSURLSessionAsyncCompletion completionWrapper = ^(id fileURLOrData, NSURLResponse *response, NSError *error) {
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordMechanism:mechanism forRequestID:requestID];
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordResponseReceivedWithRequestID:requestID
 | |
|             response:response
 | |
|         ];
 | |
|         
 | |
|         NSData *data = nil;
 | |
|         if ([fileURLOrData isKindOfClass:[NSURL class]]) {
 | |
|             data = [NSData dataWithContentsOfURL:fileURLOrData];
 | |
|         } else if ([fileURLOrData isKindOfClass:[NSData class]]) {
 | |
|             data = fileURLOrData;
 | |
|         }
 | |
|         
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordDataReceivedWithRequestID:requestID
 | |
|             dataLength:data.length
 | |
|         ];
 | |
|         
 | |
|         if (error) {
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordLoadingFailedWithRequestID:requestID
 | |
|                 error:error
 | |
|             ];
 | |
|         } else {
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                  recordLoadingFinishedWithRequestID:requestID
 | |
|                  responseBody:data
 | |
|             ];
 | |
|         }
 | |
| 
 | |
|         // Call through to the original completion handler
 | |
|         if (completion) {
 | |
|             completion(fileURLOrData, response, error);
 | |
|         }
 | |
|     };
 | |
|     return completionWrapper;
 | |
| }
 | |
| 
 | |
| + (void)injectWillSendRequestIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(connection:willSendRequest:redirectResponse:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
 | |
|     protocol = protocol ?: @protocol(NSURLConnectionDelegate);
 | |
|     struct objc_method_description methodDescription = protocol_getMethodDescription(
 | |
|         protocol, selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef NSURLRequest *(^WillSendRequestBlock)(
 | |
|         id<NSURLConnectionDelegate> slf, NSURLConnection *connection,
 | |
|         NSURLRequest *request, NSURLResponse *response
 | |
|     );
 | |
|     
 | |
|     WillSendRequestBlock undefinedBlock = ^NSURLRequest *(id slf,
 | |
|                                                           NSURLConnection *connection,
 | |
|                                                           NSURLRequest *request,
 | |
|                                                           NSURLResponse *response) {
 | |
|         [FLEXNetworkObserver.sharedObserver
 | |
|             connection:connection
 | |
|             willSendRequest:request
 | |
|             redirectResponse:response
 | |
|             delegate:slf
 | |
|         ];
 | |
|         return request;
 | |
|     };
 | |
|     
 | |
|     WillSendRequestBlock implementationBlock = ^NSURLRequest *(id slf,
 | |
|                                                                NSURLConnection *connection,
 | |
|                                                                NSURLRequest *request,
 | |
|                                                                NSURLResponse *response) {
 | |
|         __block NSURLRequest *returnValue = nil;
 | |
|         [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, connection, request, response);
 | |
|         } originalImplementationBlock:^{
 | |
|             returnValue = ((id(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, connection, request, response
 | |
|             );
 | |
|         }];
 | |
|         return returnValue;
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:methodDescription
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDidReceiveResponseIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(connection:didReceiveResponse:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
 | |
|     protocol = protocol ?: @protocol(NSURLConnectionDelegate);
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         protocol, selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidReceiveResponseBlock)(
 | |
|         id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response
 | |
|     );
 | |
|     
 | |
|     DidReceiveResponseBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                                NSURLConnection *connection,
 | |
|                                                NSURLResponse *response) {
 | |
|         [FLEXNetworkObserver.sharedObserver connection:connection
 | |
|             didReceiveResponse:response delegate:slf
 | |
|         ];
 | |
|     };
 | |
|     
 | |
|     DidReceiveResponseBlock implementationBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                                     NSURLConnection *connection,
 | |
|                                                     NSURLResponse *response) {
 | |
|         [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, connection, response);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, connection, response
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDidReceiveDataIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(connection:didReceiveData:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
 | |
|     protocol = protocol ?: @protocol(NSURLConnectionDelegate);
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         protocol, selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidReceiveDataBlock)(
 | |
|         id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data
 | |
|     );
 | |
|     
 | |
|     DidReceiveDataBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                            NSURLConnection *connection,
 | |
|                                            NSData *data) {
 | |
|         [FLEXNetworkObserver.sharedObserver connection:connection 
 | |
|             didReceiveData:data delegate:slf
 | |
|         ];
 | |
|     };
 | |
|     
 | |
|     DidReceiveDataBlock implementationBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                                 NSURLConnection *connection,
 | |
|                                                 NSData *data) {
 | |
|         [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, connection, data);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, connection, data
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(connectionDidFinishLoading:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
 | |
|     protocol = protocol ?: @protocol(NSURLConnectionDelegate);
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         protocol, selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^FinishLoadingBlock)(id<NSURLConnectionDelegate> slf, NSURLConnection *connection);
 | |
|     
 | |
|     FinishLoadingBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf, NSURLConnection *connection) {
 | |
|         [FLEXNetworkObserver.sharedObserver connectionDidFinishLoading:connection delegate:slf];
 | |
|     };
 | |
|     
 | |
|     FinishLoadingBlock implementationBlock = ^(id<NSURLConnectionDelegate> slf, NSURLConnection *connection) {
 | |
|         [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, connection);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, connection
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(connection:didFailWithError:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLConnectionDelegate), selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidFailWithErrorBlock)(
 | |
|         id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error
 | |
|     );
 | |
|     
 | |
|     DidFailWithErrorBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                              NSURLConnection *connection,
 | |
|                                              NSError *error) {
 | |
|         [FLEXNetworkObserver.sharedObserver connection:connection
 | |
|             didFailWithError:error delegate:slf
 | |
|         ];
 | |
|     };
 | |
|     
 | |
|     DidFailWithErrorBlock implementationBlock = ^(id<NSURLConnectionDelegate> slf,
 | |
|                                                   NSURLConnection *connection,
 | |
|                                                   NSError *error) {
 | |
|         [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, connection, error);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, connection, error
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectTaskWillPerformHTTPRedirectionIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionTaskDelegate), selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^HTTPRedirectionBlock)(id<NSURLSessionTaskDelegate> slf,
 | |
|                                          NSURLSession *session,
 | |
|                                          NSURLSessionTask *task,
 | |
|                                          NSHTTPURLResponse *response,
 | |
|                                          NSURLRequest *newRequest,
 | |
|                                          void(^completionHandler)(NSURLRequest *));
 | |
|     
 | |
|     HTTPRedirectionBlock undefinedBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                             NSURLSession *session,
 | |
|                                             NSURLSessionTask *task,
 | |
|                                             NSHTTPURLResponse *response,
 | |
|                                             NSURLRequest *newRequest,
 | |
|                                             void(^completionHandler)(NSURLRequest *)) {
 | |
|         [FLEXNetworkObserver.sharedObserver
 | |
|             URLSession:session task:task
 | |
|             willPerformHTTPRedirection:response
 | |
|             newRequest:newRequest
 | |
|             completionHandler:completionHandler
 | |
|             delegate:slf
 | |
|         ];
 | |
|         completionHandler(newRequest);
 | |
|     };
 | |
| 
 | |
|     HTTPRedirectionBlock implementationBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                                  NSURLSession *session,
 | |
|                                                  NSURLSessionTask *task,
 | |
|                                                  NSHTTPURLResponse *response,
 | |
|                                                  NSURLRequest *newRequest,
 | |
|                                                  void(^completionHandler)(NSURLRequest *)) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             [FLEXNetworkObserver.sharedObserver
 | |
|                 URLSession:session task:task
 | |
|                 willPerformHTTPRedirection:response
 | |
|                 newRequest:newRequest
 | |
|                 completionHandler:completionHandler
 | |
|                 delegate:slf
 | |
|             ];
 | |
|         } originalImplementationBlock:^{
 | |
|             ((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, task, response, newRequest, completionHandler
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectTaskDidReceiveDataIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:dataTask:didReceiveData:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDataDelegate), selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidReceiveDataBlock)(id<NSURLSessionDataDelegate> slf,
 | |
|                                         NSURLSession *session,
 | |
|                                         NSURLSessionDataTask *dataTask,
 | |
|                                         NSData *data);
 | |
|     DidReceiveDataBlock undefinedBlock = ^(id<NSURLSessionDataDelegate> slf,
 | |
|                                            NSURLSession *session,
 | |
|                                            NSURLSessionDataTask *dataTask,
 | |
|                                            NSData *data) {
 | |
|         [FLEXNetworkObserver.sharedObserver URLSession:session
 | |
|             dataTask:dataTask didReceiveData:data delegate:slf
 | |
|         ];
 | |
|     };
 | |
|     
 | |
|     DidReceiveDataBlock implementationBlock = ^(id<NSURLSessionDataDelegate> slf,
 | |
|                                                 NSURLSession *session,
 | |
|                                                 NSURLSessionDataTask *dataTask,
 | |
|                                                 NSData *data) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, session, dataTask, data);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, dataTask, data
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:dataTask:didBecomeDownloadTask:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDataDelegate), selector, NO, YES
 | |
|     );
 | |
| 
 | |
|     typedef void (^DidBecomeDownloadTaskBlock)(id<NSURLSessionDataDelegate> slf,
 | |
|                                                NSURLSession *session,
 | |
|                                                NSURLSessionDataTask *dataTask,
 | |
|                                                NSURLSessionDownloadTask *downloadTask);
 | |
| 
 | |
|     DidBecomeDownloadTaskBlock undefinedBlock = ^(id<NSURLSessionDataDelegate> slf,
 | |
|                                                   NSURLSession *session,
 | |
|                                                   NSURLSessionDataTask *dataTask,
 | |
|                                                   NSURLSessionDownloadTask *downloadTask) {
 | |
|         [FLEXNetworkObserver.sharedObserver URLSession:session
 | |
|             dataTask:dataTask didBecomeDownloadTask:downloadTask delegate:slf
 | |
|         ];
 | |
|     };
 | |
| 
 | |
|     DidBecomeDownloadTaskBlock implementationBlock = ^(id<NSURLSessionDataDelegate> slf,
 | |
|                                                        NSURLSession *session,
 | |
|                                                        NSURLSessionDataTask *dataTask,
 | |
|                                                        NSURLSessionDownloadTask *downloadTask) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, session, dataTask, downloadTask);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, dataTask, downloadTask
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectTaskDidReceiveResponseIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:dataTask:didReceiveResponse:completionHandler:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDataDelegate), selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidReceiveResponseBlock)(id<NSURLSessionDelegate> slf,
 | |
|                                             NSURLSession *session,
 | |
|                                             NSURLSessionDataTask *dataTask,
 | |
|                                             NSURLResponse *response,
 | |
|                                             void(^completion)(NSURLSessionResponseDisposition));
 | |
|     
 | |
|     DidReceiveResponseBlock undefinedBlock = ^(id<NSURLSessionDelegate> slf,
 | |
|                                                NSURLSession *session,
 | |
|                                                NSURLSessionDataTask *dataTask,
 | |
|                                                NSURLResponse *response,
 | |
|                                                void(^completion)(NSURLSessionResponseDisposition)) {
 | |
|         [FLEXNetworkObserver.sharedObserver
 | |
|             URLSession:session
 | |
|             dataTask:dataTask
 | |
|             didReceiveResponse:response
 | |
|             completionHandler:completion
 | |
|             delegate:slf
 | |
|         ];
 | |
|         completion(NSURLSessionResponseAllow);
 | |
|     };
 | |
|     
 | |
|     DidReceiveResponseBlock implementationBlock = ^(id<NSURLSessionDelegate> slf,
 | |
|                                                     NSURLSession *session,
 | |
|                                                     NSURLSessionDataTask *dataTask,
 | |
|                                                     NSURLResponse *response,
 | |
|                                                     void(^completion)(NSURLSessionResponseDisposition )) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             [FLEXNetworkObserver.sharedObserver
 | |
|                 URLSession:session
 | |
|                 dataTask:dataTask
 | |
|                 didReceiveResponse:response
 | |
|                 completionHandler:completion
 | |
|                 delegate:slf
 | |
|             ];
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, dataTask, response, completion
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
|     
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| 
 | |
| }
 | |
| 
 | |
| + (void)injectTaskDidCompleteWithErrorIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:task:didCompleteWithError:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
|     
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDataDelegate), selector, NO, YES
 | |
|     );
 | |
|     
 | |
|     typedef void (^DidCompleteWithErrorBlock)(id<NSURLSessionTaskDelegate> slf,
 | |
|                                               NSURLSession *session,
 | |
|                                               NSURLSessionTask *task,
 | |
|                                               NSError *error);
 | |
| 
 | |
|     DidCompleteWithErrorBlock undefinedBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                                  NSURLSession *session,
 | |
|                                                  NSURLSessionTask *task,
 | |
|                                                  NSError *error) {
 | |
|         [FLEXNetworkObserver.sharedObserver URLSession:session
 | |
|             task:task didCompleteWithError:error delegate:slf
 | |
|         ];
 | |
|     };
 | |
|     
 | |
|     DidCompleteWithErrorBlock implementationBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                                       NSURLSession *session,
 | |
|                                                       NSURLSessionTask *task,
 | |
|                                                       NSError *error) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, session, task, error);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, task, error
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| // Used for overriding AFNetworking behavior
 | |
| + (void)injectRespondsToSelectorIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(respondsToSelector:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     //Protocol *protocol = @protocol(NSURLSessionTaskDelegate);
 | |
|     Method method = class_getInstanceMethod(cls, selector);
 | |
|     struct objc_method_description methodDescription = *method_getDescription(method);
 | |
| 
 | |
|     typedef BOOL (^RespondsToSelectorImpl)(id self, SEL sel);
 | |
|     RespondsToSelectorImpl undefinedBlock = ^(id slf, SEL sel) {
 | |
|         return YES;
 | |
|     };
 | |
| 
 | |
|     RespondsToSelectorImpl implementationBlock = ^(id<NSURLSessionTaskDelegate> slf, SEL sel) {
 | |
|         if (sel == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
 | |
|             return undefinedBlock(slf, sel);
 | |
|         }
 | |
|         return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(slf, swizzledSelector, sel);
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:methodDescription
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDownloadTaskDidFinishDownloadingIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:downloadTask:didFinishDownloadingToURL:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDownloadDelegate), selector, NO, YES
 | |
|     );
 | |
| 
 | |
|     typedef void (^DidFinishDownloadingBlock)(id<NSURLSessionTaskDelegate> slf,
 | |
|                                               NSURLSession *session,
 | |
|                                               NSURLSessionDownloadTask *task,
 | |
|                                               NSURL *location);
 | |
| 
 | |
|     DidFinishDownloadingBlock undefinedBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                                  NSURLSession *session,
 | |
|                                                  NSURLSessionDownloadTask *task,
 | |
|                                                  NSURL *location) {
 | |
|         NSData *data = [NSData dataWithContentsOfFile:location.relativePath];
 | |
|         [FLEXNetworkObserver.sharedObserver URLSession:session
 | |
|             task:task didFinishDownloadingToURL:location data:data delegate:slf
 | |
|         ];
 | |
|     };
 | |
| 
 | |
|     DidFinishDownloadingBlock implementationBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                                       NSURLSession *session,
 | |
|                                                       NSURLSessionDownloadTask *task,
 | |
|                                                       NSURL *location) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(slf, session, task, location);
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, id))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, task, location
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectDownloadTaskDidWriteDataIntoDelegateClass:(Class)cls {
 | |
|     SEL selector = @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     struct objc_method_description description = protocol_getMethodDescription(
 | |
|         @protocol(NSURLSessionDownloadDelegate), selector, NO, YES
 | |
|     );
 | |
| 
 | |
|     typedef void (^DidWriteDataBlock)(id<NSURLSessionTaskDelegate> slf,
 | |
|                                       NSURLSession *session,
 | |
|                                       NSURLSessionDownloadTask *task,
 | |
|                                       int64_t bytesWritten,
 | |
|                                       int64_t totalBytesWritten,
 | |
|                                       int64_t totalBytesExpectedToWrite);
 | |
| 
 | |
|     DidWriteDataBlock undefinedBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                          NSURLSession *session,
 | |
|                                          NSURLSessionDownloadTask *task,
 | |
|                                          int64_t bytesWritten,
 | |
|                                          int64_t totalBytesWritten,
 | |
|                                          int64_t totalBytesExpectedToWrite) {
 | |
|         [FLEXNetworkObserver.sharedObserver URLSession:session
 | |
|             downloadTask:task didWriteData:bytesWritten
 | |
|             totalBytesWritten:totalBytesWritten
 | |
|             totalBytesExpectedToWrite:totalBytesExpectedToWrite
 | |
|             delegate:slf
 | |
|         ];
 | |
|     };
 | |
| 
 | |
|     DidWriteDataBlock implementationBlock = ^(id<NSURLSessionTaskDelegate> slf,
 | |
|                                               NSURLSession *session,
 | |
|                                               NSURLSessionDownloadTask *task,
 | |
|                                               int64_t bytesWritten,
 | |
|                                               int64_t totalBytesWritten,
 | |
|                                               int64_t totalBytesExpectedToWrite) {
 | |
|         [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
 | |
|             undefinedBlock(
 | |
|                 slf, session, task, bytesWritten,
 | |
|                 totalBytesWritten, totalBytesExpectedToWrite
 | |
|             );
 | |
|         } originalImplementationBlock:^{
 | |
|             ((void(*)(id, SEL, id, id, int64_t, int64_t, int64_t))objc_msgSend)(
 | |
|                 slf, swizzledSelector, session, task, bytesWritten,
 | |
|                 totalBytesWritten, totalBytesExpectedToWrite
 | |
|             );
 | |
|         }];
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfSelector:selector
 | |
|         withSelector:swizzledSelector
 | |
|         forClass:cls
 | |
|         withMethodDescription:description
 | |
|         implementationBlock:implementationBlock
 | |
|         undefinedBlock:undefinedBlock
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectWebsocketSendMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
 | |
|     SEL selector = @selector(sendMessage:completionHandler:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     typedef void (^SendMessageBlock)(
 | |
|         NSURLSessionWebSocketTask *slf,
 | |
|         NSURLSessionWebSocketMessage *message,
 | |
|         void (^completion)(NSError *error)
 | |
|     );
 | |
| 
 | |
|     SendMessageBlock implementationBlock = ^(
 | |
|         NSURLSessionWebSocketTask *slf,
 | |
|         NSURLSessionWebSocketMessage *message,
 | |
|         void (^completion)(NSError *error)
 | |
|     ) {
 | |
|         [FLEXNetworkObserver.sharedObserver
 | |
|             websocketTask:slf sendMessagage:message
 | |
|         ];
 | |
|         completion = ^(NSError *error) {
 | |
|             [FLEXNetworkObserver.sharedObserver
 | |
|                 websocketTaskMessageSendCompletion:message
 | |
|                 error:error
 | |
|             ];
 | |
|         };
 | |
|         
 | |
|         ((void(*)(id, SEL, id, id))objc_msgSend)(
 | |
|             slf, swizzledSelector, message, completion
 | |
|         );
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|         onClass:cls
 | |
|         withBlock:implementationBlock
 | |
|         swizzledSelector:swizzledSelector
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (void)injectWebsocketReceiveMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
 | |
|     SEL selector = @selector(receiveMessageWithCompletionHandler:);
 | |
|     SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
 | |
| 
 | |
|     typedef void (^SendMessageBlock)(
 | |
|         NSURLSessionWebSocketTask *slf,
 | |
|         void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
 | |
|     );
 | |
| 
 | |
|     SendMessageBlock implementationBlock = ^(
 | |
|         NSURLSessionWebSocketTask *slf,
 | |
|         void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
 | |
|     ) {        
 | |
|         id completionHook = ^(NSURLSessionWebSocketMessage *message, NSError *error) {
 | |
|             [FLEXNetworkObserver.sharedObserver
 | |
|                 websocketTask:slf receiveMessagage:message error:error
 | |
|             ];
 | |
|             completion(message, error);
 | |
|         };
 | |
|         
 | |
|         ((void(*)(id, SEL, id))objc_msgSend)(
 | |
|             slf, swizzledSelector, completionHook
 | |
|         );
 | |
| 
 | |
|     };
 | |
| 
 | |
|     [FLEXUtility replaceImplementationOfKnownSelector:selector
 | |
|         onClass:cls
 | |
|         withBlock:implementationBlock
 | |
|         swizzledSelector:swizzledSelector
 | |
|     ];
 | |
| }
 | |
| 
 | |
| static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
 | |
| 
 | |
| + (NSString *)requestIDForConnectionOrTask:(id)connectionOrTask {
 | |
|     NSString *requestID = objc_getAssociatedObject(connectionOrTask, kFLEXRequestIDKey);
 | |
|     if (!requestID) {
 | |
|         requestID = [self nextRequestID];
 | |
|         [self setRequestID:requestID forConnectionOrTask:connectionOrTask];
 | |
|     }
 | |
|     return requestID;
 | |
| }
 | |
| 
 | |
| + (void)setRequestID:(NSString *)requestID forConnectionOrTask:(id)connectionOrTask {
 | |
|     objc_setAssociatedObject(
 | |
|         connectionOrTask, kFLEXRequestIDKey, requestID, OBJC_ASSOCIATION_RETAIN_NONATOMIC
 | |
|     );
 | |
| }
 | |
| 
 | |
| #pragma mark - Initialization
 | |
| 
 | |
| - (id)init {
 | |
|     self = [super init];
 | |
|     if (self) {
 | |
|         self.requestStatesForRequestIDs = [NSMutableDictionary new];
 | |
|         self.queue = dispatch_queue_create(
 | |
|             "com.flex.FLEXNetworkObserver", DISPATCH_QUEUE_SERIAL
 | |
|         );
 | |
|     }
 | |
|     
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| #pragma mark - Private Methods
 | |
| 
 | |
| - (void)performBlock:(dispatch_block_t)block {
 | |
|     if ([[self class] isEnabled]) {
 | |
|         dispatch_async(_queue, block);
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (FLEXInternalRequestState *)requestStateForRequestID:(NSString *)requestID {
 | |
|     FLEXInternalRequestState *requestState = self.requestStatesForRequestIDs[requestID];
 | |
|     if (!requestState) {
 | |
|         requestState = [FLEXInternalRequestState new];
 | |
|         [self.requestStatesForRequestIDs setObject:requestState forKey:requestID];
 | |
|     }
 | |
|     
 | |
|     return requestState;
 | |
| }
 | |
| 
 | |
| - (void)removeRequestStateForRequestID:(NSString *)requestID {
 | |
|     [self.requestStatesForRequestIDs removeObjectForKey:requestID];
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| 
 | |
| @implementation FLEXNetworkObserver (NSURLConnectionHelpers)
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection
 | |
|    willSendRequest:(NSURLRequest *)request
 | |
|   redirectResponse:(NSURLResponse *)response
 | |
|           delegate:(id<NSURLConnectionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:connection];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         requestState.request = request;
 | |
|         
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordRequestWillBeSentWithRequestID:requestID
 | |
|             request:request
 | |
|             redirectResponse:response
 | |
|         ];
 | |
|         
 | |
|         NSString *mechanism = [NSString stringWithFormat:
 | |
|             @"NSURLConnection (delegate: %@)", [delegate class]
 | |
|         ];
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordMechanism:mechanism forRequestID:requestID];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection
 | |
| didReceiveResponse:(NSURLResponse *)response
 | |
|           delegate:(id<NSURLConnectionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:connection];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         requestState.dataAccumulator = [NSMutableData new];
 | |
| 
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordResponseReceivedWithRequestID:requestID
 | |
|             response:response
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection
 | |
|     didReceiveData:(NSData *)data
 | |
|           delegate:(id<NSURLConnectionDelegate>)delegate {
 | |
|     // Just to be safe since we're doing this async
 | |
|     data = [data copy];
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:connection];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         [requestState.dataAccumulator appendData:data];
 | |
|         
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordDataReceivedWithRequestID:requestID
 | |
|             dataLength:data.length
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)connectionDidFinishLoading:(NSURLConnection *)connection
 | |
|                           delegate:(id<NSURLConnectionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:connection];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordLoadingFinishedWithRequestID:requestID
 | |
|             responseBody:requestState.dataAccumulator
 | |
|         ];
 | |
|         [self removeRequestStateForRequestID:requestID];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)connection:(NSURLConnection *)connection
 | |
|   didFailWithError:(NSError *)error
 | |
|           delegate:(id<NSURLConnectionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:connection];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
| 
 | |
|         // Cancellations can occur prior to the willSendRequest:...
 | |
|         // NSURLConnection delegate call. These are pretty common
 | |
|         // and clutter up the logs. Only record the failure if the
 | |
|         // recorder already knows about the request through willSendRequest:...
 | |
|         if (requestState.request) {
 | |
|             [FLEXNetworkRecorder.defaultRecorder 
 | |
|                 recordLoadingFailedWithRequestID:requestID error:error
 | |
|             ];
 | |
|         }
 | |
|         
 | |
|         [self removeRequestStateForRequestID:requestID];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)connectionWillCancel:(NSURLConnection *)connection {
 | |
|     [self performBlock:^{
 | |
|         // Mimic the behavior of NSURLSession which is to create an error on cancellation.
 | |
|         NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
 | |
|         NSError *error = [NSError errorWithDomain:NSURLErrorDomain
 | |
|             code:NSURLErrorCancelled userInfo:userInfo
 | |
|         ];
 | |
|         [self connection:connection didFailWithError:error delegate:nil];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| 
 | |
| @implementation FLEXNetworkObserver (NSURLSessionTaskHelpers)
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|               task:(NSURLSessionTask *)task
 | |
| willPerformHTTPRedirection:(NSHTTPURLResponse *)response
 | |
|         newRequest:(NSURLRequest *)request
 | |
|  completionHandler:(void (^)(NSURLRequest *))completionHandler
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordRequestWillBeSentWithRequestID:requestID
 | |
|             request:request
 | |
|             redirectResponse:response
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|           dataTask:(NSURLSessionDataTask *)dataTask
 | |
| didReceiveResponse:(NSURLResponse *)response
 | |
|  completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         requestState.dataAccumulator = [NSMutableData new];
 | |
| 
 | |
|         NSString *requestMechanism = [NSString stringWithFormat:
 | |
|             @"NSURLSessionDataTask (delegate: %@)", [delegate class]
 | |
|         ];
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordMechanism:requestMechanism
 | |
|             forRequestID:requestID
 | |
|         ];
 | |
| 
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordResponseReceivedWithRequestID:requestID
 | |
|             response:response
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|           dataTask:(NSURLSessionDataTask *)dataTask
 | |
| didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         // By setting the request ID of the download task to match the data task,
 | |
|         // it can pick up where the data task left off.
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask];
 | |
|         [[self class] setRequestID:requestID forConnectionOrTask:downloadTask];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|           dataTask:(NSURLSessionDataTask *)dataTask
 | |
|     didReceiveData:(NSData *)data
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     // Just to be safe since we're doing this async
 | |
|     data = [data copy];
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
| 
 | |
|         [requestState.dataAccumulator appendData:data];
 | |
| 
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordDataReceivedWithRequestID:requestID
 | |
|             dataLength:data.length
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|               task:(NSURLSessionTask *)task
 | |
| didCompleteWithError:(NSError *)error
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
| 
 | |
|         if (error) {
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordLoadingFailedWithRequestID:requestID error:error
 | |
|             ];
 | |
|         } else {
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordLoadingFinishedWithRequestID:requestID 
 | |
|                 responseBody:requestState.dataAccumulator
 | |
|             ];
 | |
|         }
 | |
| 
 | |
|         [self removeRequestStateForRequestID:requestID];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|       downloadTask:(NSURLSessionDownloadTask *)downloadTask
 | |
|       didWriteData:(int64_t)bytesWritten
 | |
|  totalBytesWritten:(int64_t)totalBytesWritten
 | |
| totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
| 
 | |
|         if (!requestState.dataAccumulator) {
 | |
|             requestState.dataAccumulator = [NSMutableData new];
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordResponseReceivedWithRequestID:requestID
 | |
|                 response:downloadTask.response
 | |
|             ];
 | |
| 
 | |
|             NSString *requestMechanism = [NSString stringWithFormat:
 | |
|                 @"NSURLSessionDownloadTask (delegate: %@)", [delegate class]
 | |
|             ];
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordMechanism:requestMechanism
 | |
|                 forRequestID:requestID
 | |
|              ];
 | |
|         }
 | |
| 
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordDataReceivedWithRequestID:requestID
 | |
|             dataLength:bytesWritten
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSession:(NSURLSession *)session
 | |
|               task:(NSURLSessionDownloadTask *)downloadTask
 | |
| didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data
 | |
|           delegate:(id<NSURLSessionDelegate>)delegate {
 | |
|     data = [data copy];
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         [requestState.dataAccumulator appendData:data];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)URLSessionTaskWillResume:(NSURLSessionTask *)task {
 | |
|     // Since resume can be called multiple times on the same task, only treat the first resume as
 | |
|     // the equivalent to connection:willSendRequest:...
 | |
|     [self performBlock:^{
 | |
|         NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
 | |
|         FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
 | |
|         if (!requestState.request) {
 | |
|             requestState.request = task.currentRequest;
 | |
| 
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordRequestWillBeSentWithRequestID:requestID
 | |
|                 request:task.currentRequest
 | |
|                 redirectResponse:nil
 | |
|             ];
 | |
|         }
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)websocketTask:(NSURLSessionWebSocketTask *)task
 | |
|         sendMessagage:(NSURLSessionWebSocketMessage *)message {
 | |
|     [self performBlock:^{
 | |
| //        NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
 | |
|         [FLEXNetworkRecorder.defaultRecorder recordWebsocketMessageSend:message task:task];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
 | |
|                                      error:(NSError *)error {
 | |
|     [self performBlock:^{
 | |
|         [FLEXNetworkRecorder.defaultRecorder
 | |
|             recordWebsocketMessageSendCompletion:message
 | |
|             error:error
 | |
|         ];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (void)websocketTask:(NSURLSessionWebSocketTask *)task
 | |
|      receiveMessagage:(NSURLSessionWebSocketMessage *)message
 | |
|                 error:(NSError *)error {
 | |
|     [self performBlock:^{
 | |
|         if (!error && message) {
 | |
|             [FLEXNetworkRecorder.defaultRecorder
 | |
|                 recordWebsocketMessageReceived:message
 | |
|                 task:task
 | |
|             ];            
 | |
|         }
 | |
|     }];
 | |
| }
 | |
| 
 | |
| @end
 | 
