Skip to content

Commit 2f74914

Browse files
committed
chore: wip add iOS code for raw request
1 parent 4a28426 commit 2f74914

File tree

10 files changed

+1025
-630
lines changed

10 files changed

+1025
-630
lines changed

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ SPEC CHECKSUMS:
476476
React-jsi: 74341196d9547cbcbcfa4b3bbbf03af56431d5a1
477477
React-jsiexecutor: 06a9c77b56902ae7ffcdd7a4905f664adc5d237b
478478
React-jsinspector: 0ae35a37b20d5e031eb020a69cc5afdbd6406301
479-
react-native-blob-courier: 88b102ed6c8a5d31f0ca938b48fcbf2fa164fc25
479+
react-native-blob-courier: 20d24b9a54342764ee61865aafd23573c46c6f2a
480480
React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
481481
React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
482482
React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f

example/yarn.lock

Lines changed: 650 additions & 628 deletions
Large diffs are not rendered by default.

ios/BlobCourier.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ @interface RCT_EXTERN_MODULE(BlobCourier, NSObject)
1414
RCT_EXTERN_METHOD(fetchBlob:(NSDictionary *)input
1515
withResolver:(RCTPromiseResolveBlock)resolve
1616
withRejecter:(RCTPromiseRejectBlock)reject)
17+
RCT_EXTERN_METHOD(sendBlob:(NSDictionary *)input
18+
withResolver:(RCTPromiseResolveBlock)resolve
19+
withRejecter:(RCTPromiseRejectBlock)reject)
1720
RCT_EXTERN_METHOD(uploadBlob:(NSDictionary *)input
1821
withResolver:(RCTPromiseResolveBlock)resolve
1922
withRejecter:(RCTPromiseRejectBlock)reject)

ios/BlobCourier.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,35 @@ open class BlobCourier: NSObject {
6464
}
6565
}
6666

67+
@objc(sendBlob:withResolver:withRejecter:)
68+
func sendBlob(
69+
input: NSDictionary,
70+
resolve: @escaping RCTPromiseResolveBlock,
71+
reject: @escaping RCTPromiseRejectBlock
72+
) {
73+
DispatchQueue.global(qos: .background).async {
74+
do {
75+
let errorOrParameters = SenderParameterFactory.fromInput(input: input)
76+
77+
if case .failure(let error) = errorOrParameters { reject(error.code, error.message, error.error) }
78+
guard case .success(let parameters) = errorOrParameters else { return }
79+
80+
let result = BlobSender.sendBlobFromValidatedParameters(parameters: parameters)
81+
82+
switch result {
83+
case .success(let success):
84+
resolve(success)
85+
case .failure(let error):
86+
reject(error.code, error.message, error.error)
87+
}
88+
} catch {
89+
let unexpectedError = Errors.createUnexpectedError(error: error)
90+
91+
reject(unexpectedError.code, unexpectedError.message, unexpectedError.error)
92+
}
93+
}
94+
}
95+
6796
@objc(uploadBlob:withResolver:withRejecter:)
6897
func uploadBlob(
6998
input: NSDictionary,

ios/BlobCourier.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
B96A993E25BE166900F42B65 /* UploaderParameterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993B25BE166900F42B65 /* UploaderParameterFactory.swift */; };
3535
B96A993F25BE166900F42B65 /* UploadParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993C25BE166900F42B65 /* UploadParameters.swift */; };
3636
B96A994025BE166900F42B65 /* UploadParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96A993C25BE166900F42B65 /* UploadParameters.swift */; };
37+
B9854CDA261269B10054135B /* BlobSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD5261269350054135B /* BlobSender.swift */; };
38+
B9854CDD261269B60054135B /* SenderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD6261269350054135B /* SenderDelegate.swift */; };
39+
B9854CE0261269BB0054135B /* SendParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD8261269350054135B /* SendParameters.swift */; };
40+
B9854CE3261269C10054135B /* SenderParameterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9854CD9261269350054135B /* SenderParameterFactory.swift */; };
3741
B9AF759125DF263E00B68816 /* CancelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF756E25DF203800B68816 /* CancelController.swift */; };
3842
B9AF759925DF264E00B68816 /* RequestCanceller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF756F25DF203800B68816 /* RequestCanceller.swift */; };
3943
B9AF75BF25DF29DF00B68816 /* CancelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9AF75BD25DF29DF00B68816 /* CancelParameters.swift */; };
@@ -89,6 +93,10 @@
8993
B96A96FF25BDEA7D00F42B65 /* DownloaderParameterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloaderParameterFactory.swift; sourceTree = "<group>"; };
9094
B96A993B25BE166900F42B65 /* UploaderParameterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploaderParameterFactory.swift; sourceTree = "<group>"; };
9195
B96A993C25BE166900F42B65 /* UploadParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadParameters.swift; sourceTree = "<group>"; };
96+
B9854CD5261269350054135B /* BlobSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobSender.swift; sourceTree = "<group>"; };
97+
B9854CD6261269350054135B /* SenderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SenderDelegate.swift; sourceTree = "<group>"; };
98+
B9854CD8261269350054135B /* SendParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendParameters.swift; sourceTree = "<group>"; };
99+
B9854CD9261269350054135B /* SenderParameterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SenderParameterFactory.swift; sourceTree = "<group>"; };
92100
B9AF756E25DF203800B68816 /* CancelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelController.swift; sourceTree = "<group>"; };
93101
B9AF756F25DF203800B68816 /* RequestCanceller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCanceller.swift; sourceTree = "<group>"; };
94102
B9AF75BD25DF29DF00B68816 /* CancelParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelParameters.swift; sourceTree = "<group>"; };
@@ -162,6 +170,7 @@
162170
B96A888C25BC566300F42B65 /* Fetch */,
163171
B96A88AE25BC5EB800F42B65 /* Progress */,
164172
B96A945D25BD86AB00F42B65 /* React */,
173+
B9854CD4261269350054135B /* Send */,
165174
B96A889425BC567000F42B65 /* Upload */,
166175
B96829E6254EC872002B4B04 /* BlobCourierTests */,
167176
134814211AA4EA7D00B7C361 /* Products */,
@@ -228,6 +237,17 @@
228237
path = React;
229238
sourceTree = "<group>";
230239
};
240+
B9854CD4261269350054135B /* Send */ = {
241+
isa = PBXGroup;
242+
children = (
243+
B9854CD5261269350054135B /* BlobSender.swift */,
244+
B9854CD6261269350054135B /* SenderDelegate.swift */,
245+
B9854CD8261269350054135B /* SendParameters.swift */,
246+
B9854CD9261269350054135B /* SenderParameterFactory.swift */,
247+
);
248+
path = Send;
249+
sourceTree = "<group>";
250+
};
231251
B9AF756D25DF203800B68816 /* Cancel */ = {
232252
isa = PBXGroup;
233253
children = (
@@ -448,6 +468,7 @@
448468
B96A954A25BD8D3B00F42B65 /* BlobCourierEventEmitter.swift in Sources */,
449469
F4FF95D7245B92E800C19C63 /* BlobCourier.swift in Sources */,
450470
B96A993D25BE166900F42B65 /* UploaderParameterFactory.swift in Sources */,
471+
B9854CDA261269B10054135B /* BlobSender.swift in Sources */,
451472
B96829DF254EC736002B4B04 /* DownloaderDelegate.swift in Sources */,
452473
B96A8FEC25BCB87E00F42B65 /* BlobCourierEventEmitter.m in Sources */,
453474
B96A8F0925BCB1DA00F42B65 /* BlobCourier.m in Sources */,
@@ -456,12 +477,15 @@
456477
B96A8E3925BCA76300F42B65 /* BlobCourierDelayedEventEmitter.swift in Sources */,
457478
B9AF75BF25DF29DF00B68816 /* CancelParameters.swift in Sources */,
458479
B96A889625BC567000F42B65 /* BlobUploader.swift in Sources */,
480+
B9854CE3261269C10054135B /* SenderParameterFactory.swift in Sources */,
459481
B96A96D825BDE55900F42B65 /* DownloadParameters.swift in Sources */,
460482
B9AF759125DF263E00B68816 /* CancelController.swift in Sources */,
461483
B9AF75C025DF29DF00B68816 /* CancelParameterFactory.swift in Sources */,
484+
B9854CE0261269BB0054135B /* SendParameters.swift in Sources */,
462485
B96A888625BC565300F42B65 /* Errors.swift in Sources */,
463486
B96A888425BC565300F42B65 /* Constants.swift in Sources */,
464487
B96829D9254EC727002B4B04 /* UploaderDelegate.swift in Sources */,
488+
B9854CDD261269B60054135B /* SenderDelegate.swift in Sources */,
465489
B9AF759925DF264E00B68816 /* RequestCanceller.swift in Sources */,
466490
B96A993F25BE166900F42B65 /* UploadParameters.swift in Sources */,
467491
);

ios/Send/BlobSender.swift

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright (c) Ely Deckers.
2+
//
3+
// This source code is licensed under the MPL-2.0 license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
import Foundation
6+
7+
open class BlobSender: NSObject {
8+
static func filterHeaders(unfilteredHeaders: NSDictionary) -> NSDictionary {
9+
Dictionary(uniqueKeysWithValues: unfilteredHeaders
10+
.map { key, value in (key as? String, value as? String) }
11+
.filter({ $0.1 != nil }))
12+
.mapValues { $0! } as NSDictionary
13+
}
14+
15+
static func isValidTargetValue(_ value: String) -> Bool {
16+
return Constants.targetValues.contains(value)
17+
}
18+
19+
static func buildRequestDataForFileSend(
20+
method: String,
21+
url: URL,
22+
absoluteFilePath: String,
23+
headers: NSDictionary) throws -> (URLRequest, Data) {
24+
var request = URLRequest(url: url)
25+
request.httpMethod = method
26+
27+
for (key, value) in headers {
28+
if let headerKey = key as? String, let headerValue = value as? String {
29+
request.setValue(
30+
headerValue,
31+
forHTTPHeaderField: headerKey)
32+
}
33+
}
34+
35+
let fileUrl = URL(string: absoluteFilePath)!
36+
37+
let fileData = try Data(contentsOf: fileUrl)
38+
39+
return (request, fileData)
40+
}
41+
42+
// swiftlint:disable function_body_length
43+
static func sendBlobFromValidatedParameters(parameters: SendParameters) ->
44+
Result<NSDictionary, BlobCourierError> {
45+
let sessionConfig = URLSessionConfiguration.default
46+
47+
let group = DispatchGroup()
48+
let groupId = UUID().uuidString
49+
50+
let queue = DispatchQueue.global()
51+
52+
var result: Result<NSDictionary, BlobCourierError> = .success([:])
53+
54+
print("Entering group (id=\(groupId))")
55+
group.enter()
56+
57+
var cancelObserver: NSObjectProtocol?
58+
59+
queue.async(group: group) {
60+
let successfulResult = { (theResult: NSDictionary) -> Void in
61+
result = .success(theResult)
62+
63+
print("Leaving group (id=\(groupId),status=resolve)")
64+
group.leave()
65+
}
66+
67+
let failedResult = { (error: BlobCourierError) -> Void in
68+
result = .failure(error)
69+
70+
print("Leaving group (id=\(groupId),status=reject)")
71+
group.leave()
72+
}
73+
74+
let senderDelegate =
75+
SenderDelegate(
76+
taskId: parameters.taskId,
77+
returnResponse: parameters.returnResponse,
78+
progressIntervalMilliseconds: parameters.progressIntervalMilliseconds,
79+
resolve: successfulResult,
80+
reject: failedResult)
81+
82+
let session = URLSession(configuration: sessionConfig, delegate: senderDelegate, delegateQueue: nil)
83+
84+
let headers = parameters.headers
85+
86+
do {
87+
let (request, fileData) =
88+
try buildRequestDataForFileSend(
89+
method: parameters.method,
90+
url: parameters.url,
91+
absoluteFilePath: parameters.absoluteFilePath,
92+
headers: headers)
93+
94+
session.uploadTask(with: request, from: fileData).resume()
95+
96+
cancelObserver = CancelController.registerCancelObserver(
97+
session: session, taskId: parameters.taskId)
98+
} catch {
99+
failedResult(Errors.createUnexpectedError(error: error))
100+
}
101+
}
102+
103+
print("Waiting for group (id=\(groupId))")
104+
group.wait()
105+
print("Left group (id=\(groupId))")
106+
107+
NotificationCenter.default.removeObserver(cancelObserver)
108+
109+
return result
110+
}
111+
// swiftlint:enable function_body_length
112+
}

ios/Send/SendParameters.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Ely Deckers.
2+
//
3+
// This source code is licensed under the MPL-2.0 license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
import Foundation
6+
7+
struct SendParameters {
8+
let absoluteFilePath: String
9+
let headers: NSDictionary
10+
let method: String
11+
let progressIntervalMilliseconds: Int
12+
let returnResponse: Bool
13+
let taskId: String
14+
let url: URL
15+
16+
init(
17+
absoluteFilePath: String,
18+
headers: NSDictionary,
19+
method: String,
20+
progressIntervalMilliseconds: Int,
21+
returnResponse: Bool,
22+
taskId: String,
23+
url: URL) {
24+
self.absoluteFilePath = absoluteFilePath
25+
self.headers = headers
26+
self.method = method
27+
self.progressIntervalMilliseconds = progressIntervalMilliseconds
28+
self.returnResponse = returnResponse
29+
self.taskId = taskId
30+
self.url = url
31+
}
32+
}

ios/Send/SenderDelegate.swift

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Ely Deckers.
2+
//
3+
// This source code is licensed under the MPL-2.0 license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
import Foundation
6+
7+
open class SenderDelegate: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
8+
typealias SuccessHandler = (NSDictionary) -> Void
9+
typealias FailureHandler = (BlobCourierError) -> Void
10+
11+
private let resolve: SuccessHandler
12+
private let reject: FailureHandler
13+
14+
private let taskId: String
15+
private let returnResponse: Bool
16+
17+
private let eventEmitter: BlobCourierDelayedEventEmitter
18+
19+
private var receivedData: Data = Data()
20+
21+
init(
22+
taskId: String,
23+
returnResponse: Bool,
24+
progressIntervalMilliseconds: Int,
25+
resolve: @escaping SuccessHandler,
26+
reject: @escaping FailureHandler) {
27+
self.taskId = taskId
28+
self.returnResponse = returnResponse
29+
30+
self.resolve = resolve
31+
self.reject = reject
32+
33+
self.eventEmitter =
34+
BlobCourierDelayedEventEmitter(
35+
taskId: taskId,
36+
progressIntervalMilliseconds: progressIntervalMilliseconds)
37+
}
38+
39+
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
40+
}
41+
42+
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
43+
guard let theError = error else {
44+
processCompletedSend(data: self.receivedData, response: task.response, error: error)
45+
return
46+
}
47+
48+
processFailedSend(error: theError)
49+
}
50+
51+
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
52+
self.receivedData.append(data)
53+
}
54+
55+
public func urlSession(
56+
_ session: URLSession,
57+
task: URLSessionTask,
58+
didSendBodyData bytesSent: Int64,
59+
totalBytesSent: Int64,
60+
totalBytesExpectedToSend: Int64) {
61+
self.eventEmitter.notifyBridgeOfProgress(
62+
totalBytesWritten: totalBytesSent,
63+
totalBytesExpectedToWrite: totalBytesExpectedToSend)
64+
}
65+
66+
func processFailedSend(error: Error) {
67+
if (error as NSError).code == NSURLErrorCancelled {
68+
self.reject(BlobCourierError(code: Errors.errorCanceledException, message: "Request was cancelled", error: error))
69+
70+
return
71+
}
72+
73+
self.reject(Errors.createUnexpectedError(error: error))
74+
}
75+
76+
func processCompletedSend(data: Data, response: URLResponse?, error: Error?) {
77+
if let error = error {
78+
print(
79+
"Error while sending a file. Error description: \(error.localizedDescription)"
80+
)
81+
reject(Errors.createUnexpectedError(error: error))
82+
return
83+
}
84+
85+
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
86+
let maybeRawResponse = returnResponse ? String(data: data, encoding: String.Encoding.utf8) : nil
87+
let rawResponse = maybeRawResponse ?? ""
88+
89+
let result: NSDictionary = [
90+
"response": [
91+
"code": statusCode,
92+
"data": rawResponse,
93+
"headers": []
94+
]
95+
]
96+
97+
resolve(result)
98+
return
99+
}
100+
101+
let noStatusCodeError =
102+
NSError(
103+
domain: Constants.libraryDomain,
104+
code: -1,
105+
userInfo: [NSLocalizedDescriptionKey: "Received no status code"])
106+
107+
reject(Errors.createUnexpectedError(error: noStatusCodeError))
108+
}
109+
}

0 commit comments

Comments
 (0)