简体   繁体   中英

Custom URI Scheme “Unsupported URL”

Below is my info.plist and I have registered a Custom Application Query Scheme URI..

When I do an OAuth callback with redirect_uri=myapplication://oauthcallback on the Simulator or Device, I get:

Task <CC539C38-4191-48BB-B126-E41BCE28151B>.<6> load failed with 
error Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" 
UserInfo={NSLocalizedDescription=unsupported URL, 
NSErrorFailingURLStringKey=myapplication://oauthcallback?code=rGudk3a7c7&state=state-F7AF0906-984F-47C3-841B-9A55246C3784, 
NSErrorFailingURLKey=myapplication://oauthcallback?code=rGudk3a7c7&state=state-F7AF0906-984F-47C3-841B-9A55246C3784, 
_NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <CC539C38-4191-48BB-B126-E41BCE28151B>.<6>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <CC539C38-4191-48BB-B126-E41BCE28151B>.<6>, 
NSUnderlyingError=0x60000253e8e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}} [-1002]

Any ideas? Info.plist below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0.0</string>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string></string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>myapplication</string>
            </array>
        </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>tel</string>
        <string>myapplication</string>
    </array>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
    </array>
</dict>
</plist>

I figured it out. It turns out that when doing OAuth2 on iOS, the RedirectURI doesn't work! It always returns unsupported URL.. So what you have to do is create a custom URLProtocol and handle your redirectURI in there..

//
//  URLHandler.swift
//  XIO
//
//  Created by Brandon Anthony on 2018-10-01.
//  Copyright © 2018 SO. All rights reserved.
//

import Foundation

class URLHandler: URLProtocol {

  private static let requestIdentifier = "com.xio.uri.handled"

  override class func canInit(with request: URLRequest) -> Bool {
    guard request.url?.scheme == "myscheme" else {
      return false
    }

    guard let handled = URLProtocol.property(forKey: URLHandler.requestIdentifier, in: request) as? Bool else {
      return true
    }

    return !handled
  }

  override func startLoading() {
    guard let request = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
      return
    }

    URLProtocol.setProperty(true, forKey: URLHandler.requestIdentifier, in: request)

    DispatchQueue.global(qos: .background).async {
      guard let url = request.url, let headers = request.allHTTPHeaderFields else {
        self.client?.urlProtocol(self, didFailWithError: RuntimeError("URLHandler - Invalid URL."))
        return
      }

      guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headers) else {
        self.client?.urlProtocol(self, didFailWithError: RuntimeError("URLHandler - Invalid Response."))
        return
      }


      let json: [String: Any] = ["key": "value"]

      do {
        let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
        self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        self.client?.urlProtocol(self, didLoad: data as Data)
        self.client?.urlProtocolDidFinishLoading(self)
      } catch {
        self.client?.urlProtocol(self, didFailWithError: error)
      }
    }
  }

  override func stopLoading() {

  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
  }

  override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
    return super.requestIsCacheEquivalent(a, to: b)
  }
}

Alternatively, you can wait for the call to fail and then parse the response out of the NSError object.. Another solution is to launch the request and then handle the response yourself:

//
//  URLHandler.swift
//  XIO
//
//  Created by Brandon Anthony on 2018-10-01.
//  Copyright © 2018 SO. All rights reserved.
//

import Foundation

class URLHandler: URLProtocol, URLSessionDataDelegate {

  private var session: URLSession?
  private var dataTask: URLSessionDataTask?
  private static let requestIdentifier = "com.xio.uri.handled"

  override class func canInit(with request: URLRequest) -> Bool {
    guard request.url?.scheme == "myscheme" else {
      return false
    }

    guard let handled = URLProtocol.property(forKey: URLHandler.requestIdentifier, in: request) as? Bool else {
      return true
    }

    return !handled
  }

  override func startLoading() {
    guard let request = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
      return
    }

    URLProtocol.setProperty(true, forKey: URLHandler.requestIdentifier, in: request)


    var headers = request.allHTTPHeaderFields
    headers?["Accept"] = "application/json"
    headers?["Content-Type"] = "application/json"
    request.allHTTPHeaderFields = headers

    let configuration = URLSessionConfiguration.default
    self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    self.dataTask = self.session?.dataTask(with: request as URLRequest)
    self.dataTask?.resume()
  }

  override func stopLoading() {
    self.dataTask?.cancel()
    self.dataTask = nil

    self.session?.invalidateAndCancel()
    self.session = nil
  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
  }

  override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
    return super.requestIsCacheEquivalent(a, to: b)
  }

  // MARK: NSURLSessionTaskDelegate

  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error as NSError? {
      if error.code == NSURLErrorUnsupportedURL {

        guard let request = task.currentRequest else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        guard let url = request.url, let headers = request.allHTTPHeaderFields else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headers) else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        OktaTokenManager(Okta.shared).parseCode(url: url) { state, code in
          guard let state = state, let code = code else {
            self.client?.urlProtocol(self, didFailWithError: error)
            return
          }

          let json: [String: Any] = ["key": "value"]
          ]

          do {
            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
            self.client?.urlProtocol(self, didLoad: data as Data)
            self.client?.urlProtocolDidFinishLoading(self)
          } catch let err {
            print(err)
            self.client?.urlProtocol(self, didFailWithError: error)
          }
        }
        return
      }
    }

    if let response = task.response {
      client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
    }

    if let error = error { //&& error.code != NSURLErrorCancelled {
      self.client?.urlProtocol(self, didFailWithError: error)
    } else {
      self.client?.urlProtocolDidFinishLoading(self) //cacheResponse
    }
  }
}

Finally, you can just not do any of the above, and when your request fails, parse the error (similar to above) to get the real response.. I prefer this approach though.. Especially the first approach instead of launching a request.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM