// // XCallbackURL.swift // Tusker // // Created by Shadowfacts on 9/23/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit typealias XCBAction = (_ url: XCallbackURL, _ session: XCBSession, _ silent: Bool?) -> Void struct XCallbackURLSpec { let path: String let type: XCBActionType let arguments: [String: Bool] let canRunSilently: Bool let action: XCBAction init(type: XCBActionType, arguments: [String: Bool], canRunSilently: Bool, action: @escaping XCBAction) { self.path = type.path self.type = type self.canRunSilently = canRunSilently self.action = action var arguments = arguments if canRunSilently { arguments["silent"] = true } self.arguments = arguments } func handle(url: XCallbackURL) -> Bool { let session = XCBManager.createSession(type: type, url: url) if canRunSilently && url.silent { if let source = url.source { let permission = Preferences.shared.silentActions[source] ?? .undecided switch permission { case .accepted: action(url, session, true) case .rejected: action(url, session, false) case .undecided: let alert = UIAlertController(title: "\(source) wants to perform actions silently", message: "Accepting will allow \(source) to perform actions without your confirmation, rejecting will always prompt for confirmation.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { (_) in Preferences.shared.silentActions[source] = .accepted self.action(url, session, true) })) alert.addAction(UIAlertAction(title: "Reject", style: .default, handler: { (_) in Preferences.shared.silentActions[source] = .rejected self.action(url, session, false) })) UIApplication.shared.keyWindow!.rootViewController!.present(alert, animated: true) } } else { session.complete(with: .error, additionalData: [ "error": "Cannot perform silent action without source app, x-source parameter must be specified" ]) } } else { action(url, session, nil) } return true } } struct XCallbackURL { let path: String let arguments: [String: String] let silent: Bool let source: String? let success: URL? let error: URL? let cancel: URL? init(spec: XCallbackURLSpec, components: URLComponents) { self.path = spec.path let queryItems = components.queryItems! self.arguments = spec.arguments.reduce(into: [String: String](), { (result, el) in if let value = queryItems.first(where: { $0.name == el.key })?.value { result[el.key] = value } }) if spec.canRunSilently { silent = Bool(arguments["silent"] ?? "false") ?? false } else { silent = false } source = queryItems.first(where: { $0.name == "x-source" }).flatMap { $0.value } success = queryItems.first(where: { $0.name == "x-success" }).flatMap { $0.value }.flatMap { URL(string: $0) } error = queryItems.first(where: { $0.name == "x-error" }).flatMap { $0.value }.flatMap { URL(string: $0) } cancel = queryItems.first(where: { $0.name == "x-cancel" }).flatMap { $0.value }.flatMap { URL(string: $0) } } } extension XCallbackURLSpec { func matches(_ components: URLComponents) -> Bool { guard path == components.path else { return false } for (name, optional) in arguments { if (!optional && components.queryItems?.first(where: { $0.name == name }) == nil) { return false } } return true } }