[iOS] - Add ability to download and install PKPasses (Bundled) (#27258)
This commit is contained in:
+4
-2
@@ -18,7 +18,7 @@ extension BrowserViewController: WKDownloadDelegate {
|
||||
) async -> URL? {
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse {
|
||||
if httpResponse.mimeType != MIMEType.passbook {
|
||||
if ![MIMEType.passbook, MIMEType.passbookBundle].contains(httpResponse.mimeType) {
|
||||
let shouldDownload = await downloadAlert(
|
||||
download,
|
||||
response: response,
|
||||
@@ -83,7 +83,9 @@ extension BrowserViewController: WKDownloadDelegate {
|
||||
textEncodingName: downloadInfo.response.textEncodingName
|
||||
)
|
||||
|
||||
if downloadInfo.response.mimeType == MIMEType.passbook {
|
||||
if [MIMEType.passbook, MIMEType.passbookBundle].contains(
|
||||
downloadInfo.response.mimeType
|
||||
) {
|
||||
downloadQueue.download(downloadInfo, didFinishDownloadingTo: downloadInfo.fileURL)
|
||||
if let passbookHelper = OpenPassBookHelper(
|
||||
request: nil,
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ extension BrowserViewController: WKNavigationDelegate {
|
||||
return .download
|
||||
}
|
||||
|
||||
if response.mimeType == MIMEType.passbook {
|
||||
if [MIMEType.passbook, MIMEType.passbookBundle].contains(response.mimeType) {
|
||||
return .download
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,8 @@ extension HTTPDownload: URLSessionTaskDelegate, URLSessionDownloadDelegate {
|
||||
try? FileManager.default.moveItem(at: location, to: temporaryLocation)
|
||||
Task {
|
||||
do {
|
||||
let destination = try await uniqueDownloadPathForFilename(filename)
|
||||
let downloadsPath = try await AsyncFileManager.default.downloadsPath()
|
||||
let destination = try await URL.uniqueFileName(filename, in: downloadsPath)
|
||||
try await AsyncFileManager.default.moveItem(at: temporaryLocation, to: destination)
|
||||
isComplete = true
|
||||
delegate?.download(self, didFinishDownloadingTo: destination)
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import BraveCore
|
||||
import BraveShared
|
||||
import DataImporter
|
||||
import Foundation
|
||||
import MobileCoreServices
|
||||
import PassKit
|
||||
import Shared
|
||||
import UniformTypeIdentifiers
|
||||
import WebKit
|
||||
import os.log
|
||||
|
||||
struct MIMEType {
|
||||
static let bitmap = "image/bmp"
|
||||
@@ -19,6 +22,7 @@ struct MIMEType {
|
||||
static let html = "text/html"
|
||||
static let octetStream = "application/octet-stream"
|
||||
static let passbook = "application/vnd.apple.pkpass"
|
||||
static let passbookBundle = "application/vnd.apple.pkpasses"
|
||||
static let pdf = "application/pdf"
|
||||
static let plainText = "text/plain"
|
||||
static let png = "image/png"
|
||||
@@ -128,6 +132,7 @@ class DownloadHelper: NSObject {
|
||||
}
|
||||
|
||||
class OpenPassBookHelper: NSObject {
|
||||
private let mimeType: String
|
||||
fileprivate var url: URL
|
||||
|
||||
fileprivate let browserViewController: BrowserViewController
|
||||
@@ -139,29 +144,22 @@ class OpenPassBookHelper: NSObject {
|
||||
forceDownload: Bool,
|
||||
browserViewController: BrowserViewController
|
||||
) {
|
||||
guard let mimeType = response.mimeType, mimeType == MIMEType.passbook,
|
||||
guard let mimeType = response.mimeType,
|
||||
[MIMEType.passbook, MIMEType.passbookBundle].contains(mimeType.lowercased()),
|
||||
PKAddPassesViewController.canAddPasses(),
|
||||
let responseURL = response.url, !forceDownload
|
||||
else { return nil }
|
||||
self.mimeType = mimeType
|
||||
self.url = responseURL
|
||||
self.browserViewController = browserViewController
|
||||
super.init()
|
||||
}
|
||||
|
||||
@MainActor func open() async {
|
||||
let passData = try? await Task.detached { [url] in
|
||||
try Data(contentsOf: url)
|
||||
}.value
|
||||
guard let passData else { return }
|
||||
do {
|
||||
let pass = try PKPass(data: passData)
|
||||
let passLibrary = PKPassLibrary()
|
||||
if passLibrary.containsPass(pass) {
|
||||
await UIApplication.shared.open(pass.passURL!, options: [:])
|
||||
} else {
|
||||
if let addController = PKAddPassesViewController(pass: pass) {
|
||||
browserViewController.present(addController, animated: true, completion: nil)
|
||||
}
|
||||
let passes = try await parsePasses()
|
||||
if let addController = PKAddPassesViewController(passes: passes) {
|
||||
browserViewController.present(addController, animated: true, completion: nil)
|
||||
}
|
||||
} catch {
|
||||
// display an error
|
||||
@@ -179,4 +177,46 @@ class OpenPassBookHelper: NSObject {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func parsePasses() async throws -> [PKPass] {
|
||||
if mimeType == MIMEType.passbookBundle {
|
||||
let url = try await ZipImporter.unzip(path: url)
|
||||
do {
|
||||
let files = await enumerateFiles(in: url, withExtensions: ["pkpass", "pkpasses"])
|
||||
let result = try files.map { try PKPass(data: Data(contentsOf: $0)) }
|
||||
try? await AsyncFileManager.default.removeItem(at: url)
|
||||
return result
|
||||
} catch {
|
||||
try? await AsyncFileManager.default.removeItem(at: url)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
let passData = try Data(contentsOf: url)
|
||||
return try [PKPass(data: passData)]
|
||||
}
|
||||
|
||||
func enumerateFiles(
|
||||
in directory: URL,
|
||||
withExtensions extensions: [String] = []
|
||||
) async -> [URL] {
|
||||
let enumerator = AsyncFileManager.default.enumerator(
|
||||
at: directory,
|
||||
includingPropertiesForKeys: [.isRegularFileKey]
|
||||
)
|
||||
|
||||
var result: [URL] = []
|
||||
for await fileURL in enumerator {
|
||||
do {
|
||||
let resourceValues = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if resourceValues.isRegularFile == true, extensions.contains(fileURL.pathExtension) {
|
||||
result.append(fileURL)
|
||||
}
|
||||
} catch {
|
||||
Logger.module.error("Error reading file \(fileURL): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,9 +332,4 @@ extension AsyncFileManager {
|
||||
public func downloadsPath() async throws -> URL {
|
||||
try await url(for: .documentDirectory, appending: "Downloads", create: true)
|
||||
}
|
||||
|
||||
/// URL where temporary files are stored.
|
||||
public var temporaryDirectory: URL {
|
||||
fileManager.temporaryDirectory
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import BraveShared
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
class ZipImporter {
|
||||
enum ZipImportError: Error {
|
||||
public class ZipImporter {
|
||||
public enum ZipImportError: Error {
|
||||
case failedToUnzip
|
||||
case invalidFileSystemURL
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ class DownloadHelperTests: XCTestCase {
|
||||
MIMEType.html,
|
||||
MIMEType.octetStream,
|
||||
MIMEType.passbook,
|
||||
MIMEType.passbookBundle,
|
||||
MIMEType.pdf,
|
||||
MIMEType.plainText,
|
||||
MIMEType.png,
|
||||
|
||||
Reference in New Issue
Block a user