Skip to content

Commit

Permalink
chore: wip add iOS code for raw request
Browse files Browse the repository at this point in the history
  • Loading branch information
edeckers committed Dec 29, 2021
1 parent 4823491 commit a835fa4
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 1 deletion.
3 changes: 3 additions & 0 deletions ios/BlobCourier.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ @interface RCT_EXTERN_MODULE(BlobCourier, NSObject)
RCT_EXTERN_METHOD(fetchBlob:(NSDictionary *)input
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(sendBlob:(NSDictionary *)input
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(uploadBlob:(NSDictionary *)input
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
Expand Down
29 changes: 29 additions & 0 deletions ios/BlobCourier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,35 @@ open class BlobCourier: NSObject {
}
}

@objc(sendBlob:withResolver:withRejecter:)
func sendBlob(
input: NSDictionary,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
DispatchQueue.global(qos: .background).async {
do {
let errorOrParameters = SenderParameterFactory.fromInput(input: input)

if case .failure(let error) = errorOrParameters { reject(error.code, error.message, error.error) }
guard case .success(let parameters) = errorOrParameters else { return }

let result = BlobSender.sendBlobFromValidatedParameters(parameters: parameters)

switch result {
case .success(let success):
resolve(success)
case .failure(let error):
reject(error.code, error.message, error.error)
}
} catch {
let unexpectedError = Errors.createUnexpectedError(error: error)

reject(unexpectedError.code, unexpectedError.message, unexpectedError.error)
}
}
}

@objc(uploadBlob:withResolver:withRejecter:)
func uploadBlob(
input: NSDictionary,
Expand Down
24 changes: 24 additions & 0 deletions ios/BlobCourier.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
B96A993E25BE166900F42B65 /* UploaderParameterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993B25BE166900F42B65 /* UploaderParameterFactory.swift */; };
B96A993F25BE166900F42B65 /* UploadParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993C25BE166900F42B65 /* UploadParameters.swift */; };
B96A994025BE166900F42B65 /* UploadParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993C25BE166900F42B65 /* UploadParameters.swift */; };
B9854CDA261269B10054135B /* BlobSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD5261269350054135B /* BlobSender.swift */; };
B9854CDD261269B60054135B /* SenderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD6261269350054135B /* SenderDelegate.swift */; };
B9854CE0261269BB0054135B /* SendParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD8261269350054135B /* SendParameters.swift */; };
B9854CE3261269C10054135B /* SenderParameterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD9261269350054135B /* SenderParameterFactory.swift */; };
B9AF759125DF263E00B68816 /* CancelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF756E25DF203800B68816 /* CancelController.swift */; };
B9AF759925DF264E00B68816 /* RequestCanceller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF756F25DF203800B68816 /* RequestCanceller.swift */; };
B9AF75BF25DF29DF00B68816 /* CancelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF75BD25DF29DF00B68816 /* CancelParameters.swift */; };
Expand Down Expand Up @@ -87,6 +91,10 @@
B96A96FF25BDEA7D00F42B65 /* DownloaderParameterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloaderParameterFactory.swift; sourceTree = "<group>"; };
B96A993B25BE166900F42B65 /* UploaderParameterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploaderParameterFactory.swift; sourceTree = "<group>"; };
B96A993C25BE166900F42B65 /* UploadParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadParameters.swift; sourceTree = "<group>"; };
B9854CD5261269350054135B /* BlobSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobSender.swift; sourceTree = "<group>"; };
B9854CD6261269350054135B /* SenderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SenderDelegate.swift; sourceTree = "<group>"; };
B9854CD8261269350054135B /* SendParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendParameters.swift; sourceTree = "<group>"; };
B9854CD9261269350054135B /* SenderParameterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SenderParameterFactory.swift; sourceTree = "<group>"; };
B9AF756E25DF203800B68816 /* CancelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelController.swift; sourceTree = "<group>"; };
B9AF756F25DF203800B68816 /* RequestCanceller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCanceller.swift; sourceTree = "<group>"; };
B9AF75BD25DF29DF00B68816 /* CancelParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelParameters.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -162,6 +170,7 @@
B96A888C25BC566300F42B65 /* Fetch */,
B96A88AE25BC5EB800F42B65 /* Progress */,
B96A945D25BD86AB00F42B65 /* React */,
B9854CD4261269350054135B /* Send */,
B96A889425BC567000F42B65 /* Upload */,
B96829E6254EC872002B4B04 /* BlobCourierTests */,
134814211AA4EA7D00B7C361 /* Products */,
Expand Down Expand Up @@ -228,6 +237,17 @@
path = React;
sourceTree = "<group>";
};
B9854CD4261269350054135B /* Send */ = {
isa = PBXGroup;
children = (
B9854CD5261269350054135B /* BlobSender.swift */,
B9854CD6261269350054135B /* SenderDelegate.swift */,
B9854CD8261269350054135B /* SendParameters.swift */,
B9854CD9261269350054135B /* SenderParameterFactory.swift */,
);
path = Send;
sourceTree = "<group>";
};
B9AF756D25DF203800B68816 /* Cancel */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -402,6 +422,7 @@
B96A954A25BD8D3B00F42B65 /* BlobCourierEventEmitter.swift in Sources */,
F4FF95D7245B92E800C19C63 /* BlobCourier.swift in Sources */,
B96A993D25BE166900F42B65 /* UploaderParameterFactory.swift in Sources */,
B9854CDA261269B10054135B /* BlobSender.swift in Sources */,
B96829DF254EC736002B4B04 /* DownloaderDelegate.swift in Sources */,
B96A8FEC25BCB87E00F42B65 /* BlobCourierEventEmitter.m in Sources */,
B96A8F0925BCB1DA00F42B65 /* BlobCourier.m in Sources */,
Expand All @@ -410,12 +431,15 @@
B96A8E3925BCA76300F42B65 /* BlobCourierDelayedEventEmitter.swift in Sources */,
B9AF75BF25DF29DF00B68816 /* CancelParameters.swift in Sources */,
B96A889625BC567000F42B65 /* BlobUploader.swift in Sources */,
B9854CE3261269C10054135B /* SenderParameterFactory.swift in Sources */,
B96A96D825BDE55900F42B65 /* DownloadParameters.swift in Sources */,
B9AF759125DF263E00B68816 /* CancelController.swift in Sources */,
B9AF75C025DF29DF00B68816 /* CancelParameterFactory.swift in Sources */,
B9854CE0261269BB0054135B /* SendParameters.swift in Sources */,
B96A888625BC565300F42B65 /* Errors.swift in Sources */,
B96A888425BC565300F42B65 /* Constants.swift in Sources */,
B96829D9254EC727002B4B04 /* UploaderDelegate.swift in Sources */,
B9854CDD261269B60054135B /* SenderDelegate.swift in Sources */,
B9AF759925DF264E00B68816 /* RequestCanceller.swift in Sources */,
B96A993F25BE166900F42B65 /* UploadParameters.swift in Sources */,
);
Expand Down
112 changes: 112 additions & 0 deletions ios/Send/BlobSender.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Ely Deckers.
//
// This source code is licensed under the MPL-2.0 license found in the
// LICENSE file in the root directory of this source tree.
import Foundation

open class BlobSender: NSObject {
static func filterHeaders(unfilteredHeaders: NSDictionary) -> NSDictionary {
Dictionary(uniqueKeysWithValues: unfilteredHeaders
.map { key, value in (key as? String, value as? String) }
.filter({ $0.1 != nil }))
.mapValues { $0! } as NSDictionary
}

static func isValidTargetValue(_ value: String) -> Bool {
return Constants.targetValues.contains(value)
}

static func buildRequestDataForFileSend(
method: String,
url: URL,
absoluteFilePath: String,
headers: NSDictionary) throws -> (URLRequest, Data) {
var request = URLRequest(url: url)
request.httpMethod = method

for (key, value) in headers {
if let headerKey = key as? String, let headerValue = value as? String {
request.setValue(
headerValue,
forHTTPHeaderField: headerKey)
}
}

let fileUrl = URL(string: absoluteFilePath)!

let fileData = try Data(contentsOf: fileUrl)

return (request, fileData)
}

// swiftlint:disable function_body_length
static func sendBlobFromValidatedParameters(parameters: SendParameters) ->
Result<NSDictionary, BlobCourierError> {
let sessionConfig = URLSessionConfiguration.default

let group = DispatchGroup()
let groupId = UUID().uuidString

let queue = DispatchQueue.global()

var result: Result<NSDictionary, BlobCourierError> = .success([:])

print("Entering group (id=\(groupId))")
group.enter()

var cancelObserver: NSObjectProtocol?

queue.async(group: group) {
let successfulResult = { (theResult: NSDictionary) -> Void in
result = .success(theResult)

print("Leaving group (id=\(groupId),status=resolve)")
group.leave()
}

let failedResult = { (error: BlobCourierError) -> Void in
result = .failure(error)

print("Leaving group (id=\(groupId),status=reject)")
group.leave()
}

let senderDelegate =
SenderDelegate(
taskId: parameters.taskId,
returnResponse: parameters.returnResponse,
progressIntervalMilliseconds: parameters.progressIntervalMilliseconds,
resolve: successfulResult,
reject: failedResult)

let session = URLSession(configuration: sessionConfig, delegate: senderDelegate, delegateQueue: nil)

let headers = parameters.headers

do {
let (request, fileData) =
try buildRequestDataForFileSend(
method: parameters.method,
url: parameters.url,
absoluteFilePath: parameters.absoluteFilePath,
headers: headers)

session.uploadTask(with: request, from: fileData).resume()

cancelObserver = CancelController.registerCancelObserver(
session: session, taskId: parameters.taskId)
} catch {
failedResult(Errors.createUnexpectedError(error: error))
}
}

print("Waiting for group (id=\(groupId))")
group.wait()
print("Left group (id=\(groupId))")

NotificationCenter.default.removeObserver(cancelObserver)

return result
}
// swiftlint:enable function_body_length
}
32 changes: 32 additions & 0 deletions ios/Send/SendParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Ely Deckers.
//
// This source code is licensed under the MPL-2.0 license found in the
// LICENSE file in the root directory of this source tree.
import Foundation

struct SendParameters {
let absoluteFilePath: String
let headers: NSDictionary
let method: String
let progressIntervalMilliseconds: Int
let returnResponse: Bool
let taskId: String
let url: URL

init(
absoluteFilePath: String,
headers: NSDictionary,
method: String,
progressIntervalMilliseconds: Int,
returnResponse: Bool,
taskId: String,
url: URL) {
self.absoluteFilePath = absoluteFilePath
self.headers = headers
self.method = method
self.progressIntervalMilliseconds = progressIntervalMilliseconds
self.returnResponse = returnResponse
self.taskId = taskId
self.url = url
}
}
109 changes: 109 additions & 0 deletions ios/Send/SenderDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Ely Deckers.
//
// This source code is licensed under the MPL-2.0 license found in the
// LICENSE file in the root directory of this source tree.
import Foundation

open class SenderDelegate: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
typealias SuccessHandler = (NSDictionary) -> Void
typealias FailureHandler = (BlobCourierError) -> Void

private let resolve: SuccessHandler
private let reject: FailureHandler

private let taskId: String
private let returnResponse: Bool

private let eventEmitter: BlobCourierDelayedEventEmitter

private var receivedData: Data = Data()

init(
taskId: String,
returnResponse: Bool,
progressIntervalMilliseconds: Int,
resolve: @escaping SuccessHandler,
reject: @escaping FailureHandler) {
self.taskId = taskId
self.returnResponse = returnResponse

self.resolve = resolve
self.reject = reject

self.eventEmitter =
BlobCourierDelayedEventEmitter(
taskId: taskId,
progressIntervalMilliseconds: progressIntervalMilliseconds)
}

public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
}

public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let theError = error else {
processCompletedSend(data: self.receivedData, response: task.response, error: error)
return
}

processFailedSend(error: theError)
}

public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.receivedData.append(data)
}

public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64) {
self.eventEmitter.notifyBridgeOfProgress(
totalBytesWritten: totalBytesSent,
totalBytesExpectedToWrite: totalBytesExpectedToSend)
}

func processFailedSend(error: Error) {
if (error as NSError).code == NSURLErrorCancelled {
self.reject(BlobCourierError(code: Errors.errorCanceledException, message: "Request was cancelled", error: error))

return
}

self.reject(Errors.createUnexpectedError(error: error))
}

func processCompletedSend(data: Data, response: URLResponse?, error: Error?) {
if let error = error {
print(
"Error while sending a file. Error description: \(error.localizedDescription)"
)
reject(Errors.createUnexpectedError(error: error))
return
}

if let statusCode = (response as? HTTPURLResponse)?.statusCode {
let maybeRawResponse = returnResponse ? String(data: data, encoding: String.Encoding.utf8) : nil
let rawResponse = maybeRawResponse ?? ""

let result: NSDictionary = [
"response": [
"code": statusCode,
"data": rawResponse,
"headers": []
]
]

resolve(result)
return
}

let noStatusCodeError =
NSError(
domain: Constants.libraryDomain,
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Received no status code"])

reject(Errors.createUnexpectedError(error: noStatusCodeError))
}
}
Loading

0 comments on commit a835fa4

Please sign in to comment.