diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.h b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.h new file mode 100644 index 000000000..81ac460ab --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.h @@ -0,0 +1,24 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#if FB_SONARKIT_ENABLED + +#import + +#import +#import "SKTigonObserver.h" + +@interface SKTigonAdapter: NSObject +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.mm b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.mm new file mode 100644 index 000000000..3c5f8fc99 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonAdapter.mm @@ -0,0 +1,47 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import "SKTigonAdapter.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@implementation SKTigonAdapter +{ + std::unique_ptr _observerToken; + std::unique_ptr _observerDebugToken; +} +@synthesize delegate = _delegate; + +- (void)setDelegate:(id)delegate { + _delegate = delegate; + auto listener = std::make_shared(_delegate); + + _observerToken = [[FBHttpExecutor sharedStack] addObserver:listener]; + _observerDebugToken = [[FBHttpExecutor sharedStack] addDebugObserver:listener]; + +} + +- (void)dealloc { + if (_observerToken) { + _observerToken->remove(); + _observerToken = nullptr; + } + if (_observerDebugToken) { + _observerDebugToken->remove(); + _observerDebugToken = nullptr; + } +} + +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKDispatchQueueMock.h b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKDispatchQueueMock.h new file mode 100644 index 000000000..1ea12a980 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKDispatchQueueMock.h @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#pragma once + +#import + +#import + +namespace facebook { + namespace sonar { + class SyncQueue: public DispatchQueue + { + public: + SyncQueue() + :_isSuspended(NO){} + + void async(dispatch_block_t block) override + { + if (_isSuspended) { + _blockArray.push_back(block); + } else { + block(); + } + } + + void suspend() + { + _isSuspended = YES; + } + + void resume() + { + _isSuspended = NO; + for (const auto &block : _blockArray) { + block(); + } + } + + private: + std::vector _blockArray; + bool _isSuspended; + }; + } +} + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.h b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.h new file mode 100644 index 000000000..20a6891f6 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.h @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import + +#import +@interface SKTigonNetworkPluginMock : SonarKitNetworkPlugin + +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.mm b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.mm new file mode 100644 index 000000000..9c4bf862b --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTestUtils/SKTigonNetworkPluginMock.mm @@ -0,0 +1,17 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import "SKTigonNetworkPluginMock.h" + +#import "SKDispatchQueueMock.h" + +@implementation SKTigonNetworkPluginMock + +- (instancetype)initWithNetworkAdapter:(id)adapter { + return [super initWithNetworkAdapter:adapter queue:std::make_shared()]; +} + +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKBufferingPluginTests.mm b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKBufferingPluginTests.mm new file mode 100644 index 000000000..78926cec3 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKBufferingPluginTests.mm @@ -0,0 +1,160 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import + +#import +#import +#import +#import + +static FBMonotonicTimeMilliseconds sendRequestInfoForIdentifier(NSInteger identifier, SKBufferingPlugin *plugin) +{ + FBMonotonicTimeMilliseconds requestTimestamp = FBMonotonicTimeGetCurrentMilliseconds(); + + [plugin send:@"newRequest" sonarObject:@{ + @"id": @(identifier), + @"timestamp": @(requestTimestamp), + }]; + + return requestTimestamp; +} + +@interface SKBufferingPluginTests : XCTestCase + +@end + +@implementation SKBufferingPluginTests + +- (void)testCacheAfterSuccessfulConnection { + SKBufferingPlugin *plugin = [[SKBufferingPlugin alloc] initWithQueue:std::make_shared()]; + + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, plugin); + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + FBMonotonicTimeMilliseconds timestamp3 = sendRequestInfoForIdentifier(3, plugin); + + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @3, + @"timestamp": @(timestamp3), + }])); +} + +- (void)testCacheAfterDisconnection { + SKBufferingPlugin *plugin = [[SKBufferingPlugin alloc] initWithQueue:std::make_shared()]; + + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, plugin); + [plugin didDisconnect]; + connection.connected = NO; + FBMonotonicTimeMilliseconds timestamp3 = sendRequestInfoForIdentifier(3, plugin); + + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + }])); + XCTAssertFalse(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @3, + @"timestamp": @(timestamp3), + }])); +} + +- (void)testCacheAfterDisconnectionAndConnection { + SKBufferingPlugin *plugin = [[SKBufferingPlugin alloc] initWithQueue:std::make_shared()]; + + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, plugin); + [plugin didDisconnect]; + connection.connected = NO; + FBMonotonicTimeMilliseconds timestamp3 = sendRequestInfoForIdentifier(3, plugin); + connection.connected = YES; + [plugin didConnect: connection]; + + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @3, + @"timestamp": @(timestamp3), + }])); +} + +- (void)testLossOfEventsDueToDisconnection { + auto queue = std::make_shared(); + SKBufferingPlugin *plugin = [[SKBufferingPlugin alloc] initWithQueue:queue]; + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, plugin); + queue->suspend(); + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + [plugin didDisconnect]; + connection.connected = NO; + queue->resume(); + XCTAssertFalse(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + }])); + XCTAssertFalse(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + }])); +} + +- (void)testCacheInQueueSuspension { + auto queue = std::make_shared(); + SKBufferingPlugin *plugin = [[SKBufferingPlugin alloc] initWithQueue:queue]; + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, plugin); + queue->suspend(); + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + queue->resume(); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + }])); +} + +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKTigonNetworkPluginTests.mm b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKTigonNetworkPluginTests.mm new file mode 100644 index 000000000..2bbe1a363 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonNetworkPluginTests/SKTigonNetworkPluginTests.mm @@ -0,0 +1,190 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import + +#import +#import + +#import +#import +#import +#import +#import + +static BOOL isResponseStrippedForContentType(NSString *contentType) +{ + SKTigonNetworkPluginMock *plugin = [[SKTigonNetworkPluginMock alloc] initWithNetworkAdapter:[SKTigonAdapter new]]; + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + id delegate = (id)plugin; + + NSURL *url = [NSURL URLWithString: @"http://fakeurl"]; + NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL: url + statusCode: 200 + HTTPVersion: @"1.1" + headerFields: @{ + @"content-type": contentType + }]; + + FBMonotonicTimeMilliseconds responseTimestamp = FBMonotonicTimeGetCurrentMilliseconds(); + NSData *responseBody = [@"some-response-data" dataUsingEncoding: NSUTF8StringEncoding]; + + ResponseInfo responseInfo = { + .identifier = 1, + .timestamp = responseTimestamp, + .response = response, + .body = nil, + }; + responseInfo.setBody(responseBody); + + [delegate didObserveResponse:responseInfo]; + id data = connection.sent[@"newResponse"][0][@"data"]; + + return [data isEqual:[NSNull null]]; +} + +static FBMonotonicTimeMilliseconds sendRequestInfoForIdentifier(NSInteger identifier, NSData *requestBody, SonarKitNetworkPlugin *plugin) +{ + id delegate = (id)plugin; + NSURL *url = [NSURL URLWithString: @"http://fakeurl"]; + NSMutableURLRequest *request = [[NSURLRequest requestWithURL: url] mutableCopy]; + [request setHTTPMethod: @"POST"]; + [request addValue: @"somevalue" forHTTPHeaderField:@"some-header-field"]; + + FBMonotonicTimeMilliseconds requestTimestamp = FBMonotonicTimeGetCurrentMilliseconds(); + + RequestInfo requestInfo = { + .identifier = identifier, + .timestamp = requestTimestamp, + .request = request, + .body = nil, + }; + requestInfo.setBody(requestBody); + [delegate didObserveRequest:requestInfo]; + return requestTimestamp; +} + +@interface SKTigonNetworkPluginTests : XCTestCase +@end + +@implementation SKTigonNetworkPluginTests + +- (void)testPluginIsObserverDelegate +{ + SonarKitNetworkPlugin *plugin = [[SKTigonNetworkPluginMock alloc] initWithNetworkAdapter:[SKTigonAdapter new]]; + XCTAssertTrue([plugin conformsToProtocol: @protocol(SKNetworkReporterDelegate)], @"SKTigonNetworkPlugin should conform to SKTigonObserverDelegate"); +} + +- (void)testObserveRequest +{ + SKTigonNetworkPluginMock *plugin = [[SKTigonNetworkPluginMock alloc] initWithNetworkAdapter:[SKTigonAdapter new]]; + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + id delegate = (id)plugin; + + NSURL *url = [NSURL URLWithString: @"http://fakeurl"]; + NSMutableURLRequest *request = [[NSURLRequest requestWithURL: url] mutableCopy]; + [request setHTTPMethod: @"POST"]; + [request addValue: @"somevalue" forHTTPHeaderField:@"some-header-field"]; + + FBMonotonicTimeMilliseconds requestTimestamp = FBMonotonicTimeGetCurrentMilliseconds(); + NSData *requestBody = [@"some-request-data" dataUsingEncoding: NSUTF8StringEncoding]; + + RequestInfo requestInfo = { + .identifier = 1, + .timestamp = requestTimestamp, + .request = request, + .body = nil, + }; + requestInfo.setBody(requestBody); + + [delegate didObserveRequest:requestInfo]; + + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(requestTimestamp), + @"method": @"POST", + @"url": @"http://fakeurl", + @"headers": @[ @{ + @"key": @"some-header-field", + @"value": @"somevalue" + }], + @"data": [requestBody base64EncodedStringWithOptions: 0] + }])); +} + +- (void)testObserveRequestWithCache { + SKTigonNetworkPluginMock *plugin = [[SKTigonNetworkPluginMock alloc] initWithNetworkAdapter:[SKTigonAdapter new]]; + NSData *requestBody = [@"some-request-data" dataUsingEncoding: NSUTF8StringEncoding]; + FBMonotonicTimeMilliseconds timestamp1 = sendRequestInfoForIdentifier(1, requestBody, plugin); + FBMonotonicTimeMilliseconds timestamp2 = sendRequestInfoForIdentifier(2, requestBody, plugin); + FBMonotonicTimeMilliseconds timestamp3 = sendRequestInfoForIdentifier(3, requestBody, plugin); + SonarConnectionMock *connection = [SonarConnectionMock new]; + [plugin didConnect: connection]; + FBMonotonicTimeMilliseconds timestamp4 = sendRequestInfoForIdentifier(4, requestBody, plugin); + + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @1, + @"timestamp": @(timestamp1), + @"method": @"POST", + @"url": @"http://fakeurl", + @"headers": @[ @{ + @"key": @"some-header-field", + @"value": @"somevalue" + }], + @"data": [requestBody base64EncodedStringWithOptions: 0] + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @2, + @"timestamp": @(timestamp2), + @"method": @"POST", + @"url": @"http://fakeurl", + @"headers": @[ @{ + @"key": @"some-header-field", + @"value": @"somevalue" + }], + @"data": [requestBody base64EncodedStringWithOptions: 0] + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @3, + @"timestamp": @(timestamp3), + @"method": @"POST", + @"url": @"http://fakeurl", + @"headers": @[ @{ + @"key": @"some-header-field", + @"value": @"somevalue" + }], + @"data": [requestBody base64EncodedStringWithOptions: 0] + }])); + XCTAssertTrue(([connection.sent[@"newRequest"] + containsObject: @{ + @"id": @4, + @"timestamp": @(timestamp4), + @"method": @"POST", + @"url": @"http://fakeurl", + @"headers": @[ @{ + @"key": @"some-header-field", + @"value": @"somevalue" + }], + @"data": [requestBody base64EncodedStringWithOptions: 0] + }])); +} + +- (void)testStripBinaryResponse +{ + XCTAssertTrue(isResponseStrippedForContentType(@"image/jpeg")); + XCTAssertTrue(isResponseStrippedForContentType(@"image/jpg")); + XCTAssertTrue(isResponseStrippedForContentType(@"image/png")); + XCTAssertTrue(isResponseStrippedForContentType(@"video/mp4")); + XCTAssertTrue(isResponseStrippedForContentType(@"application/zip")); +} + +@end + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.h b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.h new file mode 100644 index 000000000..db79593cd --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.h @@ -0,0 +1,37 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +#if FB_SONARKIT_ENABLED + +#import + +#import +#import +#import +#import + +class SKTigonObserver : public facebook::tigon::TigonObserver, public facebook::tigon::TigonDebugObserver { +public: + SKTigonObserver(id notifier); + + void onAdded(std::shared_ptr requestAdded) override; + + void onStarted(std::shared_ptr requestStarted) override; + + void onResponse(std::shared_ptr requestResponse) override; + + void onEOM(std::shared_ptr requestSucceeded) override; + + void onError(std::shared_ptr requestErrored) override; + + void onWillRetry(std::shared_ptr requestWillRetry) override {}; + + void onUploadBody(const std::shared_ptr &requestUploadBody) override; + + void onDownloadBody(const std::shared_ptr &requestDownloadBody) override; + +protected: + id _delegate; + std::unordered_map _trackedRequests; + std::unordered_map _trackedResponses; +}; + +#endif diff --git a/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.mm b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.mm new file mode 100644 index 000000000..6450e3b53 --- /dev/null +++ b/iOS/Plugins/Facebook/SKTigonNetworkPlugin/SKTigonObserver.mm @@ -0,0 +1,115 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#if FB_SONARKIT_ENABLED + +#import "SKTigonObserver.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +SKTigonObserver::SKTigonObserver(id delegate) : _delegate(delegate) {} + +void SKTigonObserver::onAdded(std::shared_ptr requestAdded) { + NSURLRequest *request = facebook::FBTigon::fromTigonRequest(requestAdded->submittedTigonRequest()); + _trackedRequests[requestAdded->requestId()] = { + .identifier = requestAdded->requestId(), + .request = request, + .timestamp = FBMonotonicTimeGetCurrentMilliseconds(), + }; +} + +void SKTigonObserver::onStarted(std::shared_ptr requestStarted) { + NSURLRequest *request = facebook::FBTigon::fromTigonRequest(requestStarted->submittedTigonRequest()); + _trackedRequests[requestStarted->requestId()] = { + .identifier = requestStarted->requestId(), + .request = request, + .timestamp = FBMonotonicTimeGetCurrentMilliseconds(), + }; +} + +void SKTigonObserver::onResponse(std::shared_ptr requestResponse) { + NSURLRequest *request = facebook::FBTigon::fromTigonRequest(requestResponse->sentTigonRequest()); + NSHTTPURLResponse *response = facebook::FBTigon::fromTigonResponse(requestResponse->tigonResponse(), [request URL]); + _trackedResponses[requestResponse->requestId()] = { + .identifier = requestResponse->requestId(), + .timestamp = FBMonotonicTimeGetCurrentMilliseconds(), + .response = response, + .body = nil + }; +} + +void SKTigonObserver::onError(std::shared_ptr requestErrored) { + auto result = _trackedResponses.find(requestErrored->requestId()); + if (result == _trackedResponses.end()) { + NSURLRequest *request = facebook::FBTigon::fromTigonRequest(requestErrored->sentTigonRequest()); + NSHTTPURLResponse *response = facebook::FBTigon::fromTigonResponse(requestErrored->tigonResponse(), [request URL]); + [_delegate didObserveResponse:{ + .identifier = requestErrored->requestId(), + .timestamp = FBMonotonicTimeGetCurrentMilliseconds(), + .response = response, + .body = nil + }]; + } else { + ResponseInfo &responseInfo = _trackedResponses[requestErrored->requestId()]; + responseInfo.timestamp = FBMonotonicTimeGetCurrentMilliseconds(); + [_delegate didObserveResponse:responseInfo]; + } +} + +void SKTigonObserver::onEOM(std::shared_ptr requestSucceeded) { + if (_trackedRequests.count(requestSucceeded->requestId()) != 0) { + RequestInfo &requestInfo = _trackedRequests[requestSucceeded->requestId()]; + [_delegate didObserveRequest:requestInfo]; + _trackedRequests.erase(requestSucceeded->requestId()); + } + + ResponseInfo &responseInfo = _trackedResponses[requestSucceeded->requestId()]; + responseInfo.timestamp = FBMonotonicTimeGetCurrentMilliseconds(); + [_delegate didObserveResponse:responseInfo]; + _trackedResponses.erase(requestSucceeded->requestId()); +} + +void SKTigonObserver::onUploadBody(const std::shared_ptr &requestUploadBody) { + RequestInfo &requestInfo = _trackedRequests[requestUploadBody->requestId()]; + + NSURLRequest *urlRequest = requestInfo.request; + + NSString *contentType = [urlRequest valueForHTTPHeaderField: @"Content-Type"]; + BOOL isFormData = [contentType hasPrefix: @"application/x-www-form-urlencoded"] || + [contentType hasPrefix: @"multipart/form-data"]; + + if (requestUploadBody->body() && isFormData) { + NSString *contentEncoding = [urlRequest valueForHTTPHeaderField: @"Content-Encoding"]; + BOOL isGzip = [contentEncoding isEqualToString: @"gzip"]; + + NSData *data = facebook::FBTigon::toNSData(requestUploadBody->body()); + if (isGzip) { + data = [data newDataByDecompressingWithGZIP]; + } + + requestInfo.setBody(data); + } + + [_delegate didObserveRequest:requestInfo]; + _trackedRequests.erase(requestUploadBody->requestId()); +} + +void SKTigonObserver::onDownloadBody(const std::shared_ptr &requestDownloadBody) { + if (requestDownloadBody->body()) { + ResponseInfo &responseInfo = _trackedResponses[requestDownloadBody->requestId()]; + NSData *data = facebook::FBTigon::toNSData(requestDownloadBody->body()); + responseInfo.setBody(data); + _trackedResponses[requestDownloadBody->requestId()] = responseInfo; + } +} + +#endif