Uploading a PDF File From Swift Yields a Blank PDF - swift

Im trying to upload a PDF generated from a UIImage. I take a picture using my camera framework, uploads all ok but the PDF on S3 is blank. Below is the code I am using:
I use the following class to create the PDF from a UIImage, tested ok.
// Using PDFKit
func generatePDF(source: UIImage) -> PDFDocument {
let pdfDocument = PDFDocument()
let pdfPage = PDFPage(image: source)
pdfDocument.insert(pdfPage!, at: 0)
return pdfDocument
}
I used Alamofire to perform a multipart upload with the pdf, tested ok.
class NetworkManager {
static let shared = NetworkManager()
func upload(document: Data, name: String) {
let filename = "\(name).pdf"
let urlString = endpoint + "?filename=" + filename
let url = URL(string: urlString)!
var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 30)
request.method = .post
request.setValue(key, forHTTPHeaderField: api)
AF.upload(multipartFormData: { multiPart in
multiPart.append(document, withName: name, fileName: filename, mimeType: "application/pdf")
}, with: request)
.uploadProgress(queue: .main, closure: { progress in
print("Upload Progress: \(progress.fractionCompleted)")
})
.responseJSON(completionHandler: { data in
print("data: \(data)")
})
}
}
On AWS I am using the following framework to reconstruct the multipart data back to a PDF
lambda-multipart-parser
https://www.npmjs.com/package/lambda-multipart-parser
How Im using it in Lambda:
let filename = event.queryStringParameters.filename;
let documentData = await imageParser.parse(event);
let document = documentData.files[0];
var data = {
Bucket: 'order-scanned-copy',
Key: filename,
Body: document.content,
ContentType: "application/pdf",
};
let uploadFile = await s3.putObject(data).promise();
No errors, no warnings just a blank PDF. I can tell by the size around 500kb-1mb that it has something in it. Any help would be appreciated.
Thanks.

I figured it out, since I am using AWS specifically API Gateway and Lambda I needed to add the "Binary Media Type" to API Gateway. To do this I opened Api Gateway console and choose my API. Then, selected Settings and added the following types.
I did a redeploy of my api and everything worked perfect, so in the end it was not a code problem but configuration mistake on AWS.
This blog post was very helpful:
AWS SAM Configuration for API Gateway Binary Response / Payloads
Thanks for your help everyone.

Related

confused on how to create the Stripe ephemeral key in the APIClient.swift class

So for the last 2 days I've been stumped on how to implement this Stripe API, it's by far been the hardest thing to wrap my head around with. So I decided to integrate the Stripe functionality using Firebase and Cloud Functions and I've been seeing that it's server-less which is great.
I've been trying to follow this article on iOS Stripe API integration and this article showing how to create the cloud functions involving Stripe and I so far have been able to create a Stripe customer upon new user creation. After that, I'm pretty much lost on how to do what I want to do next, which is create ephemeral keys.
I have this function I snagged from another SO post:
exports.createEphemeralKeys = functions.https.onRequest((req, res) => {
var api_version = req.body.api_version;
var customerId = req.body.customerId;
if (!api_version) {
res.status(400).end();
return;
}
stripe.ephemeralKeys.create(
{ customer: customerId },
{ stripe_version: api_version },
function(err, key) {
return res.send(key);
});
});
And this is the method in the MyAPIClient.swift file:
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
urlComponents.queryItems = [URLQueryItem(name: "api_version", value: apiVersion)]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, error)
return
}
completion(json, nil)
})
task.resume()
}
Now this is where I get confused, since integrating Stripe with Firebase is server-less, what are we supposed to input in the baseURL? Currently the baseURL variable is empty, but I'm also getting thrown errors that .appendingPathComponent is not available as well. Forgive me if this is a dumb question, but I'd much rather look like a complete idiot and eventually figure out how to successfully integrate this API, than not ask anything at all. Thanks in advance.
The baseURL should be set to the URL where your Firebase functions live. Have a look at the Firebase documentation for invoking an HTTP function for details.
The URL will be something like this:
https://<region>-<project-id>.cloudfunctions.net/

Log alamofire upload image request and response

I know there is a lot about this issue but I can't find exactly the one related to uploading. I'm using alamofire for my HTTP calls.
I'm trying to upload the image to Amazon S3 and getting an error, wanting to log request and response to see what exactly problem is.
All my requests are logging except Alamofire.upload. I added an extension to request and Aramofire.request is logging and now my problem is to log also Alamofire.upload.
As an example, I'm trying to log request and response for the following example.
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in params {
multipartFormData.append(value.data(using: String.Encoding.utf8, allowLossyConversion: false)!, withName: key)
}
if image != nil {
if let imgData = UIImageJPEGRepresentation(image!, 0.8) {
multipartFormData.append(imgData, withName: "file", fileName: "file.jpg", mimeType: "image/jpeg")
}
}
}, to: "amazon.s3.URL")
After digging into SessionManager class I found out the way how to log upload request and response.
I override the upload function.
Here is my example
class AlamofireManager: SessionManager {
override func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
let request = super.upload(data, with: urlRequest)
print("Request: \(request.debugDescription)")
print("Response: \(request.response.data)")
return request
}
}
You can also use this pod https://github.com/konkab/AlamofireNetworkActivityLogger and you can call it in applicationDidLaunch.. it will log everything pretty nice.

Pass an uploaded image directly from json file

I am trying to upload an image from my app to my heroku backend and then pass that to Stripe for verification. Here is my swift code to upload and pass the image:
#IBAction func registerAccountButtonWasPressed(sender: UIButton) {
let dob = self.dobTextField.text!.components(separatedBy: "/")
let URL = "https://splitterstripeservertest.herokuapp.com/account/create"
var imageData = UIImageJPEGRepresentation(photoID,1)
let params = ["year": UInt(dob[2])! as UInt] as [String : Any]
let manager = AFHTTPSessionManager()
let operation = manager.post(URL, parameters: params, constructingBodyWith: { (formData: AFMultipartFormData!) -> Void in
formData.appendPart(withFileData: imageData!, name: "file", fileName: "photoID.jpg", mimeType: "image/jpeg")
}, success: { (operation, responseObject) -> Void in
print(responseObject!)
}) { (operation, error) -> Void in
self.handleError(error as NSError)
}
}
I've deleted the list of params above out and left one for readability.
Is there a way to then receive this file and upload it to stripe without having to save it by passing the file parameter? like so:
Stripe::FileUpload.create(
{
:purpose => 'identity_document',
:file => params[file]
},
{:stripe_account => params[stripe_account]}
)
Also in the stripe docs it says to upload the file to 'https://uploads.stripe.com/v1/files' but then shows code to put in your backend, does Stripe::FileUpload.create do the uploading to stripe for me?
Any insight on either would be great thanks.
You need to first upload the file to Stripe's API using the "create file upload" call. You can then use the file upload's ID (file_...) to update the account's legal_entity.verification.document attribute.
(This process is explained here:
https://stripe.com/docs/connect/identity-verification#uploading-a-file
https://stripe.com/docs/connect/identity-verification#attaching-the-file)
Since the file is provided by the user, you have two choices for creating the file upload:
have your app upload the file to your backend server, then on your backend, use the file to create the file upload
create the file upload directly from the app (using your publishable API key), and send the resulting file_upload's ID (file_...) to your backend
Here's an example for creating file uploads client-side, using jQuery: https://jsfiddle.net/captainapollo/d8yd3761/.
You could do the same thing from your iOS app's code. Basically all you need to do is send a POST request to https://uploads.stripe.com/v1/files with an Authorization header with value Bearer pk_... (where pk_... is your publishable API key) and type multipart/form-data, with the file's contents as the request's body. This blog post should be helpful for sending multipart/form-data requests using Swift: http://www.kaleidosblog.com/how-to-upload-images-using-swift-2-send-multipart-post-request
Thanks to #Ywain I was able to come up with this solution for IOS Swift 4. I created the file upload directly from the app and retrieved the file_upload's ID to send to my backend to attach to the Connect Account. I did this by importing Alamofire and SwiftyJSON.
let heads = ["Authorization": "Bearer \(stripePublishableKey)"]
let imageData = image!.jpegData(compressionQuality: 1.0)
let fileName = "\(NSUUID().uuidString).jpeg"
Alamofire.upload(multipartFormData: { multipart in
multipart.append(imageData!, withName: "file", fileName: fileName, mimeType: "image/jpeg")
multipart.append(("identity_document").data(using: .utf8)!, withName :"purpose")
}, to: fileUrl!, method: .post, headers: heads) { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { answer in
let value = JSON(answer.value!)
let FileID = value["id"].stringValue
// Use fileID and Connect Account ID to create params to send to backend.
let params: [String: Any] = [
"acct_id": ConnectID!,
"fileID": FileID,
]
print("statusCode: \(String(describing: answer.response?.statusCode))")
}
case .failure(let encodingError):
print("multipart upload encodingError: \(encodingError)")
}
}

Images not caching with Firebase url but caching with other

I have little problem. I got caching to work with the following URL:
let URL = NSURL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!
But can't get it to work like this with this URL:
FIRStorage.storage().reference().child("\(productImageref!).png").downloadURLWithCompletion({(url, error)in
if error != nil{
print(error)
return
}else{
cell.snusProductImageView.kf_setImageWithURL(url , placeholderImage: nil,
optionsInfo: [.Transition(ImageTransition.Fade(1))],
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
}
})
What I am doing wrong here? Can you point me to the right direction? For caching I use the 3rd party library "KingFisher"
Edit: Firebase guy Mike McDonald's quote
"The Github one has Cache-Control: max-age=300 while Firebase Storage
doesn't have cache control set by default (you can set it when you
upload the file, or change it by updating metadata), so I assume
that's why KingFisher isn't caching it."
KingFisher owner quotes.
Like Firebase guy Mike McDonald said I directly set the cache-control when uploading the image to the Firebase storage. Then it will be cached correctly when loading it later.
(Configuration: Swift3, Firebase Storage 3.11.0)
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: yourFirebaseStorageReferenceURL)
let imageRef = storageRef.child("profileImages/\(imageUUID).jpg")
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
// THIS is the important part
metadata.cacheControl = "public,max-age=2592000"
let uploadTask = imageRef.put(imageData, metadata: metadata) { (metadata, error) in
if let error = error
{
completion(.failure(error as NSError))
}
guard let metadata = metadata, let downloadURL = metadata.downloadURL()?.absoluteString else {
// Handle upload error
return
}
// Handle successful upload
}
When I download the image, the cache-control is set
The way I got them caching was that I had to write all image url-s into JSON like this:
.setValue(url!.absoluteString)
And then read those as url-s

RESTful mechanism to upload video (and properties) to vimeo

The procedure to upload a video to Vimeo starts out very similarly as the one defined for Youtube, but only up to a point. Below I describe the steps that worked, and outline the last video-upload step which does not:
The Vimeo-upload dance begins when we pass the following parameters to trigger user authentication:
let authPath:String = "\(url_vauth)?response_type=\(response_type)&client_id=\(client_id)&redirect_uri=\(redirect_uri)&state=\(state)&scope=upload"
if let authURL:NSURL = NSURL(string: authPath) {
let request = NSURLRequest(URL: authURL)
webView.loadRequest(request) // opens a webpage in a webUIView
// once credentials are entered, google redirects back with the above uri + a temporary code
// we will exchange later for a token
// within AppDelegate, we have defined a way to handle this uri, which is to call
// processOAuthStep1Response(url)
Then, we process the returned response to extract an authorization code:
let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
var auth_code:String!
// the block below extracts the text that follows "code" in the return url
if let queryItems = components?.queryItems {
for queryItem in queryItems { // step through each part of url
if queryItem.name.lowercaseString == "code" {
auth_code = queryItem.value
break
} // end of if queryItem.name.lowercaseString
} // end of for
} // if let queryItems
With this authorization code, we then generate a token:
let getTokenPath:String = url_token
let grant_type = "authorization_code"
let header_plain = "\(client_id):\(client_secret)"
let string_plain = header_plain.dataUsingEncoding(NSUTF8StringEncoding)
let string_base64 = (string_plain?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)))! as String
let headers = ["Authorization": "basic \(string_base64)"] // note that string_base64 really needs to be in base64!
//print ("...header is: \(string_base64)")
let tokenParams = ["grant_type": grant_type, "code": receivedCode, "redirect_uri": redirect_uri, "scope": "public"]
let request = Alamofire.request(.POST, getTokenPath, parameters: tokenParams, encoding: .URL, headers: headers)
We use this token to generate a ticket:
request(.POST, url_getticket, parameters: ticketParams , encoding: .URL, headers: headers).responseJSON { response in
//print(response)
switch response.result {
case .Success(let data):
let json = JSON(data)
print (json)
let myticket = json["ticket_id"].stringValue
//let usage = json[("upload_quota")].stringValue
let htmlform = json[("form")].stringValue
let uploadlink = json[("upload_link_secure")].stringValue
print("......ticket is \(myticket)")
print("......form is \(htmlform)")
print("......upload link is \(uploadlink)")
case .Failure(let error):
print("Request failed with error: \(error)")
} // end of switch
Finally (and this is where things stop to a screeching halt) we are supposed to use this ticket to make a POST request to Vimeo. The problem is that this ticket is embedded in an html form that actually makes the upload request to Vimeo...Not very useful for the iOS platform where I'm trying to implement this. Ideally, I'd like implementing with Alamofire via an upload call like so:
let headers = ["Authorization": "Bearer \(token)"]
upload(
.POST,
"https://1511923767.cloud.vimeo.com/upload?ticket_id=#######&video_file_id=####&signature=####&v6=1&redirect_url=https%3A%2F%2Fvimeo.com%2Fupload%2Fapi%3Fvideo_file_id%3D498216063%26app_id%3D70020%26ticket_id%####%26signature%######",
headers: headers,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: videodata, name: "video", fileName: "bagsy.m4v", mimeType: "application/octet-stream")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
dispatch_async(dispatch_get_main_queue()) {
let percent = (Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
//progress(percent: percent)
print ("................\(percent)")
}
}
upload.validate()
upload.responseJSON { response in
print(response)
callback(true)
}
case .Failure(_):
callback(false)
}
})
Needless to say, the chunk of code above does not work. Any guidance would be most appreciated.
Consider using the official Vimeo iOS Upload SDK. We made it public about 2 weeks ago. It's a Swift library that handles upload of video files to Vimeo servers. It does so using a background-configured NSURLSession (so uploads continue regardless of whether your app is in the foreground or background). Let us know if you have any questions. Note: I'm one of the authors of the library and I work at Vimeo.
The VimeoUpload README is pretty robust and should communicate all that you need to know. Lettuce know if you have additional questions though, or feel free to make a pull request.
The ticket is never manually used in an upload. You should always use the url, or html provided by the API.
If you see HTML, it's because you are not providing the "type" parameter. We default to a simple POST upload system as described here: https://developer.vimeo.com/api/upload/videos#simple-http-post-uploading
If you provide "type=streaming", Vimeo returns a "complete_url", which you must call after performing the streaming upload as described here: https://developer.vimeo.com/api/upload/videos#resumable-http-put-uploads