Robust Swift networking for web APIs

Overview

Conduit

Release CocoaPods Compatible Platform

Conduit is a session-based Swift HTTP networking and auth library.

Within each session, requests are sent through a serial pipeline before being dispatched to the network queue. Within the pipeline, requests are processed through a collection of middleware that can decorate requests, pause the session pipeline, and empty the outgoing queue. From this pattern, Conduit bundles pre-defined middleware for OAuth2 authorization grants through all major flows defined within RFC 6749 and automatically applies tokens to requests as defined in RFC 6750.

Features

  • Session-based network clients
  • Configurable middleware for outbound requests
  • Powerful HTTP request construction and serialization
  • JSON, XML, SOAP, URL-encoded, and Multipart Form serialization and response deserialization
  • Complex query parameter serialization
  • Cancellable/pausable session tasks with upload/download progress closures
  • SSL pinning / server trust policies
  • Network Reachability
  • OAuth2 client management
  • Automatic token refreshes, client_credential grants, and token storage
  • Secure token storage with AES-256 CBC encryption
  • Full manual control over all token grants within RFC 6749
  • Automatic bearer/basic token application
  • Embedded authorization page / authorization code grant strategies
  • Support for multiple network sessions / OAuth2 clients
  • Interfaces for migrating from pre-existing networking layers

Requirements

  • iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 8.1+
Conduit Version Swift Version
0.4.x 3.x
0.5 - 0.7.x 4.0
0.8 - 0.13.x 4.1
0.14.0 - 0.17.x 4.2
0.18.0+ 5.0

Installation

Swift Package Manager (recommended)

Add Conduit to your Package.swift:

// swift-tools-version:5.0
import PackageDescription

let package = Package(
    dependencies: [
        .package(url: "https://github.com/mindbody/Conduit.git", from: "1.0.0")
    ]
)

Cocoapods

Add Conduit to your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

target 'MyApplicationTarget' do
    pod 'Conduit'
end

Core Networking

URLSessionClient

The heart and soul of Conduit is URLSessionClient. Each client is backed by a URLSession; therefore, URLSessionClient's are initialized with an optional URLSessionConfiguration and a delegate queue.

// Creates a new URLSessionClient with no persistent cache storage and that fires events on a background queue
let mySessionClient = URLSessionClient(sessionConfiguration: URLSessionConfiguration.ephemeral, delegateQueue: OperationQueue())

URLSessionClient is a struct, meaning that it uses value semantics. After initializing a URLSessionClient, any copies can be mutated directly without affecting other copies. However, multiple copies of a single client will use the same network pipeline; they are still part of a single session. In other words, a URLSessionClient should only ever be initialized once per network session.

class MySessionClientManager {

    /// Lazy-loaded URLSessionClient used for interacting with the Kittn API 🐱
    static let kittnAPISessionClient: URLSessionClient = {
        return URLSessionClient()
    }()

}

/// Example usage ///

var sessionClient = MySessionClientManager.kittnAPISessionClient

// As a copy, this won't mutate the original copy or any other copies
sessionClient.middleware = [MyCustomMiddleware()]

HTTP Requests / Responses

URLSessionClient would be nothing without URLRequest's to send to the network. In order to scale against many different possible transport formats within a single session, URLSessionClient has no sense of serialization or deserialization; instead, we fully construct and serialize a URLRequest with an HTTPRequestBuilder and a RequestSerializer and then manually deserialize the response with a ResponseDeserializer.

let requestBuilder = HTTPRequestBuilder(url: kittensRequestURL)
requestBuilder.method = .GET
// Can be serialzed via url-encoding, XML, or multipart/form-data
requestBuilder.serializer = JSONRequestSerializer()
// Powerful query string formatting options allow for complex query parameters
requestBuilder.queryStringParameters = [
    "options" : [
        "include" : [
            "fuzzy",
            "fluffy",
            "not mean"
        ],
        "2+2" : 4
    ]
]
requestBuilder.queryStringFormattingOptions.dictionaryFormat = .dotNotated
requestBuilder.queryStringFormattingOptions.arrayFormat = .commaSeparated
requestBuilder.queryStringFormattingOptions.spaceEncodingRule = .replacedWithPlus
requestBuilder.queryStringFormattingOptions.plusSymbolEncodingRule = .replacedWithDecodedPlus

let request = try requestBuilder.build()

let sessionClient = MySessionClientManager.kittnAPISessionClient
sessionClient.begin(request) { (data, response, error) in
    let deserializer = JSONResponseDeserializer()
    let responseDict = try? deserializer.deserialize(response: response, data: data) as? [String : Any]
    ...
}

The MultipartFormRequestSerializer uses predetermined MIME types to heavily simplify multipart/form-data request construction.

let serializer = MultipartFormRequestSerializer()
let kittenImage = UIImage(named: "jingles")
let kittenImageFormPart = FormPart(name: "kitten", filename: "mr-jingles.jpg", content: .image(kittenImage, .jpeg(compressionQuality: 0.8)))
let pdfFormPart = FormPart(name: "pedigree", filename: "pedigree.pdf", content: .pdf(pedigreePDFData))
let videoFormPart = FormPart(name: "cat-video", filename: "cats.mov", content: .video(catVideoData, .mov))

serializer.append(formPart: kittenImageFormPart)
serializer.append(formPart: pdfFormPart)
serializer.append(formPart: videoFormPart)

requestBuilder.serializer = serializer

XMLRequestSerializer and XMLResponseDeserializer utilize the project-defined XML and XMLNode. XML data is automatically parsed into an indexable and subscriptable tree.

let requestBodyXMLString = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request>give me cats</Request>"

requestBuilder.requestSerializer = XMLRequestSerializer()
requestBuilder.method = .POST
requestBuilder.bodyParameters = XML(xmlString: requestBodyXMLString)

Middleware

When a request is sent through a URLSessionClient, it is first processed serially through a pipeline that may potentially contain middleware. Each middleware component may modify the request, cancel the request, or freeze the pipeline altogether.

Network Pipeline Architecture

This could be used for logging, proxying, authorization, and implementing strict network behaviors.

/// Simple middelware example that logs each outbound request
struct LoggingRequestPipelineMiddleware: RequestPipelineMiddleware {

    public func prepareForTransport(request: URLRequest, completion: @escaping Result<Void>.Block) {
        print("Outbound request: \(request)")
    }

}

mySessionClient.middleware = [LoggingRequestPipelineMiddleware()]

SSL Pinning

Server trust evaluation is built right in to URLSessionClient. A ServerAuthenticationPolicy evaluates session authentication challenges. The most common server authentication request is the start of a TLS/SSL connection, which can be verified with an SSLPinningServerAuthenticationPolicy.

Since it's possible that a single session client may interact with disconnected third-party hosts, the initializer requires a predicate that determines whether or not the trust chain should be pinned against.

let sslPinningPolicy = SSLPinningServerAuthenticationPolicy(certificates: CertificateBundle.certificatesInBundle) { challenge in
    // All challenges from other hosts will be ignored and will proceed through normal system evaluation
    return challenge.protectionSpace.host == "api.example.com"
}

mySessionClient.serverAuthenticationPolicies = [sslPinningPolicy]

Auth

Conduit implements all major OAuth2 flows and intricacies within RFC 6749 and RFC 6750. This makes Conduit an ideal foundational solution for OAuth2-based API SDK's.

Configuration

Every Auth session requires a client configuration, which, in turn, requires an OAuth2 server environment.

guard let tokenGrantURL = URL(string: "https://api.example.com/oauth2/issue/token") else {
    return
}

let scope = "cats dogs giraffes"
let serverEnvironment = OAuth2ServerEnvironment(scope: scope, tokenGrantURL: tokenGrantURL)

let clientID = "my_oauth2_client"
let clientSecret = "shhhh"

let clientConfiguration = OAuth2ClientConfiguration(clientIdentifier: clientID, clientSecret: clientSecret, environment: serverEnvironment)

// Only for convenience for single-client applications; can be managed elsewhere
Auth.defaultClientConfiguration = clientConfiguration

Token Storage

OAuth2 token storage allows for automatic retrieval/updates within token grant flows.

// Stores user and client tokens to the keychain
let keychainStore = OAuth2KeychainStore(serviceName: "com.company.app-name.oauth-token", accessGroup: "com.company.shared-access-group")

// Stores user and client tokens to UserDefaults or a defined storage location
let diskStore = OAuth2TokenDiskStore(storageMethod: .userDefaults)

// Stores user and client tokens to memory; useful for tests/debugging
let memoryStore = OAuth2TokenMemoryStore()

// Only for convenience for single-client applications; can be managed elsewhere
Auth.defaultTokenStore = keychainStore

Token Grants

OAuth2 token grants are handled via strategies. Conduit supports all grants listed in RFC 6749: password, client_credentials, authorization_code, refresh_token, and custom extension grants.

In many places throughout Conduit Auth, an OAuth2Authorization is required. OAuth2Authorization is a simple struct that segregates client authorization from user authorization, and Bearer credentials from Basic credentials. While certain OAuth2 servers may not actually respect these as different roles or identities, it allows for clear-cut management over user-sensitive data vs. client-sensitive data.

When manually creating and using an OAuth2TokenGrantStrategy (common for Resource Owner flows), tokens must also be manually stored:

// This token grant is most-likely issued on behalf of a user, so the authorization level is "user", and the authorization type is "bearer"
let tokenGrantStrategy = OAuth2PasswordTokenGrantStrategy(username: "[email protected]", password: "hunter2", clientConfiguration: Auth.defaultClientConfiguration)
tokenGrantStrategy.issueToken { result in
    guard case .value(let token) = result else {
        // Handle failure
        return
    }
    let userBearerAuthorization = OAuth2Authorization(type: .bearer, level: .user)
    Auth.defaultTokenStore.store(token: token, for: Auth.defaultClientConfiguration, with: userBearerAuthorization)
    // Handle success
}
// This token grant is issued on behalf of a client, so the authorization level is "client"
let tokenGrantStrategy = OAuth2ClientCredentialsTokenGrantStrategy(clientConfiguration: Auth.defaultClientConfiguration)
tokenGrantStrategy.issueToken { result in
    guard case .value(let token) = result else {
        // Handle failure
        return
    }
    let clientBearerAuthorization = OAuth2Authorization(type: .bearer, level: .client)
    Auth.defaultTokenStore.store(token: token, for: Auth.defaultClientConfiguration, with: clientBearerAuthorization)
    // Handle success
}

For the Authorization Code flow, there exists OAuth2AuthorizationStrategy. Currently, implementation only exists for iOS Safari.

// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    OAuth2AuthorizationRedirectHandler.default.authorizationURLScheme = "x-my-custom-scheme"
    // Other setup
    return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    if OAuth2AuthorizationRedirectHandler.default.handleOpen(url) {
        return true
    }
    ...
}
// SampleAuthManager.swift

guard let authorizationBaseURL = URL(string: "https://api.example.com/oauth2/authorize"),
    let redirectURI = URL(string: "x-my-custom-scheme://authorize") else {
    return
}
let authorizationStrategy = OAuth2SafariAuthorizationStrategy(presentingViewController: visibleViewController, authorizationRequestEndpoint: authorizationBaseURL)

var authorizationRequest = OAuth2AuthorizationRequest(clientIdentifier: "my_oauth2_client")
authorizationRequest.redirectURI = redirectURI
authorizationRequest.scope = "cats dogs giraffes"
authorizationRequest.state = "abc123"
authorizationRequest.additionalParameters = [
    "custom_param_1" : "value"
]

authorizationStrategy.authorize(request: authorizationRequest) { result in
    guard case .value(let response) = result else {
        // Handle failure
        return
    }
    if response.state != authorizationRequest.state {
        // We've been attacked! 👽
        return
    }
    let tokenGrantStrategy = OAuth2AuthorizationCodeTokenGrantStrategy(code: response.code, redirectURI: redirectURI, clientConfiguration: Auth.defaultClientConfiguration)

    tokenGrantStrategy.issueToken { result in
        // Store token
        // Handle success/failure
    }
}

Auth Middleware

Tying it all together, Conduit provides middleware that handles most of dirty work involved with OAuth2 clients. This briefly sums up the power of OAuth2RequestPipelineMiddleware:

  • Automatically applies stored Bearer token for the given OAuth2 client if one exists and is valid
  • Pauses/empties the outbound network queue and attempts a refresh_token grant for expired tokens, if a refresh token exists
  • Attempts a client_credentials grant for client-bearer authorizations if the token is expired or doesn't exist
  • Automatically applies Basic authorization for client-basic authorizations

When fully utilized, Conduit makes service operations extremely easy to read and understand, from the parameters/encoding required all the way to the exact type and level of authorization needed:

let requestBuilder = HTTPRequestBuilder(url: protectedKittensRequestURL)
requestBuilder.method = .GET
requestBuilder.serializer = JSONRequestSerializer()
requestBuilder.queryStringParameters = [
    "options" : [
        "include" : [
            "fuzzy",
            "fluffy",
            "not mean"
        ],
        "2+2" : 4
    ]
]
requestBuilder.queryStringFormattingOptions.dictionaryFormat = .dotNotated
requestBuilder.queryStringFormattingOptions.arrayFormat = .commaSeparated
requestBuilder.queryStringFormattingOptions.spaceEncodingRule = .replacedWithPlus
requestBuilder.queryStringFormattingOptions.plusSymbolEncodingRule = .replacedWithDecodedPlus

let request = try requestBuilder.build()

let bearerUserAuthorization = OAuth2Authorization(type: .bearer, level: .user)
let authMiddleware = OAuth2RequestPipelineMiddleware(clientConfiguration: Auth.defaultClientConfiguration, authorization: userBearerAuthorization, tokenStorage: Auth.defaultTokenStore)

var sessionClient = MySessionClientManager.kittnAPISessionClient
// Again, this is a copy, so we're free to mutate the middleware within the copy
sessionClient.middleware.append(authMiddleware)

sessionClient.begin(request) { (data, response, error) in
    let deserializer = JSONResponseDeserializer()
    let responseDict = try? deserializer.deserialize(response: response, data: data) as? [String : Any]
    ...
}

Examples

This repo includes an iOS example, which is attached to Conduit.xcworkspace

License

Released under the Apache 2.0 license. See LICENSE for more details.

Credits

mindbody-logo

Conduit is owned by MINDBODY, Inc. and continuously maintained by our contributors.

Comments
  • (Feature)|Add customParameters to OAuth2AuthorizationResponse

    (Feature)|Add customParameters to OAuth2AuthorizationResponse

    This pull request includes (pick all that apply):

    • [ ] Bugfixes
    • [x] New features/Feature Gap
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [x] Unit tests
    • [ ] Other

    Summary

    This change will allow consuming applications of third party APIs to react accordingly when the user does not approve all requested scope permissions.

    Implementation

    • Added scope property to the OAuth2AuthorizationResponse object as an array of Strings.
    • Hooked this property up in the OAuth2AuthorizationRedirectHandler. The query string parameter scope is converted into a String array split by commas. This value is optional as users may choose to accept all requested permissions or none.

    Test Plan

    • Added test to OAuth2AuthorizationCodeTokenGrantStrategyTests to verify the scope is not altered.
    • Tested in live OAuth2SafariAuthorizationStrategy using Strava API, accepting and denying requested scope permissions.
    enhancement help wanted 
    opened by anthony-lipscomb-dev 12
  • Improve RFC 3986 conformance in QueryStringFormattingOptions

    Improve RFC 3986 conformance in QueryStringFormattingOptions

    Summary

    I'd like to propose making it easier to follow RFC 3986 URI-Encoding rules "to the letter."

    Section 3.2 identifies the following general delimiters and subdelimiters:

    • General: :#[]@?/
    • Subdelimiters: !$&'()*+,;=

    (Section 3.4 says that "?" and "/" should NOT be escaped in query strings, however.)

    To get Conduit to percent-encode all these characters, you would need to (some) of these characters need to the percentEncodedReservedCharacterSet property of QueryStringFormattingOptions, which is non-obvious.

    It should be noted that Alamofire always percent-encodes these 16 characters (the 18 delimiters, minus ? and /). While "matching Alamofire" isn't a goal of Conduit, I'd argue that its percent-encoding behavior is, to a degree, battle-tested and expected in iOS apps.

    Notes/thoughts/ramblings

    URI percent-encoding behavior seems to vary from place to place across platforms/frameworks/languages. Obviously, it's a bit of a rat's nest. A few examples:

    • https://www.urlencoder.org/ (claims to follow RFC 3986, but percent-encodes ? and /)

    • Ruby's URI::encode_www_form_component method (Ruby docs reference https://www.w3.org/TR/html5/forms.html#url-encoded-form-data.. encodes ? and /)

    • Chrome's implementation of encodeURIComponent(str). (Does not encode !, ', (, or )) It's documentation interestingly notes that a "stringent" adherrence to RFC 3986 would look like:

    function fixedEncodeURIComponent(str) {
      return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
        return '%' + c.charCodeAt(0).toString(16);
      });
    }
    

    Suggestions/thoughts

    1. Define static constants of CharacterSet representing the most common patterns that can be passed to QueryStringFormattingOptions

    2. Make QueryStringFormattingOptions default behavior match a stringent implementation of RFC 3986.

    3. Implement CustomDebugStringConvertible on QueryStringFormattingOptions. The debug output can include the list of characters that will be percent-encoded in a query string.

    4. The Conduit documentation for percentEncodedReservedCharacterSet references section 6.2.2, which, as best I can tell, is not pertinent to the topic of percent-encoding query strings. I believe this may be a typo.


    If any action items spring from this issue & its discussion, I'd love to tackle the implementation.

    enhancement 
    opened by matt-holden 7
  • (Bugfix Issue 137) | Crash in URLSessionClient

    (Bugfix Issue 137) | Crash in URLSessionClient

    This pull request includes (pick all that apply):

    • [x] Bugfixes
    • [ ] New features
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [ ] Other

    Summary

    • A crash can sometimes occur in URLSessionClient
    • https://github.com/mindbody/Conduit/issues/137

    Implementation

    Moved dictionary indexing for a completion block collection to the an existing serial queue to avoid edge-case crashes

    Test Plan

    Utilize the framework

    bug 
    opened by brettwellmanmbo 6
  • (Feature)|Add support for multi-session coordination and app containers

    (Feature)|Add support for multi-session coordination and app containers

    This pull request includes (pick all that apply):

    • [x] Bugfixes
    • [x] New features
    • [x] Breaking changes
    • [x] Documentation updates
    • [x] Unit tests
    • [ ] Other

    Summary

    While Conduit technically can support session handoff between processes via file storage + group containers or via keychain groups, there is still a lack of support for concurrent processes, which makes either approach impractical. The single point of failure for this being possible is within the token refresh workflow. If two processes happen to concurrently process middleware for a unified session with an expired token, then the loser will send up a refresh token that has already been used and force both sessions to end. This experience is less than ideal.

    Interprocess communication is hard outside of macOS. I've explored many different potential architectures using the few options available (primarily Darwin notifications and NSFilePresenter). After months of scrapping countless paths, I've landed on an approach that further drives Conduit's primary goal, which is simplicity. This only focuses on the target point of failure described above, and it does so using existing interfaces. To break it down:

    • When a session begins a refresh, a new method on OAuth2TokenStore allows the middleware to lock the refresh token for a period of time
    • If an alternate session hits the same code branch before the refresh completes, then it waits to observe an unlock, or until the lock expires (in case of process termination or other errors)
    • When the refresh completes, the refresh token is unlocked via token storage, and a system notification is fired via CFNotificationCenterGetDarwinNotifyCenter. These simple notifications are dispatched through Core OS and can be observed by all processes running on a device
    • The alternate session then re-processes the parameters via async recursion

    As for the actual token storage, consumers are limited to unencrypted file storage, in which I've discovered a bug/flaw in design that prevents multiple OAuth2 clients, or multiple authorization levels within each client, from writing to a single location. Plain and simple, OAuth2TokenDiskStore was ill-defined and messy, and the expansion of state-based logic was a clear sign of too many responsibilities. This deprecates OAuth2TokenDiskStore in favor of OAuth2TokenUserDefaultsStore and OAuth2TokenFileStore. The new file storage requires a directory instead of a file path, which solves the design problem. It also allows each type of storage to be focused and to scale with improvements as we see fit.

    Implementation

    DarwinNotificationCenter

    • High-level wrapper for CFNotificationCenterGetDarwinNotifyCenter
    • Currently only used to register observers for token refresh completion

    OAuth2TokenRefreshCoordinator

    • Communicates with DarwinNotificationCenter to register token refresh observers
    • Used directly by OAuth2RequestPipelineMiddleware between refreshes

    OAuth2TokenFileStore

    • Stores token to local file, given a storage directory
    • Allows for file coordination when multiple sessions / processes may be involved
    • Allows for custom writing options, such as file protection

    OAuth2TokenUserDefaultsStore

    • Same as OAuth2TokenDiskStore(storageMethod: .userDefaults), except nonstandard UserDefaults (i.e. from app group containers) are permitted

    Deprecated: OAuth2TokenDiskStore

    Test Plan

    Tests have been added to cover new workflows and additional storage interfaces.

    opened by johnhammerlund 6
  • (Bugfix)|Add SwiftLint exception to clear false-positive (duplicate_enum_case)

    (Bugfix)|Add SwiftLint exception to clear false-positive (duplicate_enum_case)

    This pull request includes (pick all that apply):

    • [x] Bugfixes
    • [ ] New features
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [ ] Other

    Summary

    A recent release of SwiftLint includes a new implicit rule, duplicate_enum_cases. This rule doesn't consider conditional compilation flags, as described in the open issue here: https://github.com/realm/SwiftLint/issues/2782

    For now, I've added an exception to allow builds to proceed.

    Implementation

    Added a SwiftLint exception surrounding the false-positive in MultipartFormRequestSerializer.swift

    Test Plan

    Build should pass with the latest version of SwiftLint installed

    bug 
    opened by johnhammerlund 4
  • ConduitError and SessionTaskResponse

    ConduitError and SessionTaskResponse

    This pull request includes (pick all that apply):

    • [ ] Bugfixes
    • [x] New features
    • [x] Breaking changes
    • [ ] Documentation updates
    • [x] Unit tests
    • [ ] Other

    Summary

    This pull request covers three major points in regards of error handling and request responses.

    • Simplifies error handling by condensing all error types into ConduitError.
    • Adds support for LocalizedError protocol, for a much improved debugging/logging experience.
    • Condenses response tuples (data, response, error) into a single, easier to handle, SessionTaskResponse structure.

    ConduitError

    At the moment, error handling in Conduit is good enough, but it can be improved and simplified. There is a long list of error types that Conduit can return, which can make it hard for consuming applications to do pattern matching.

    OAuth2Error, DataConversionError, RequestSerializerError, ResponseDeserializerError, XMLError, URLError, URLSessionClientError, are all now returned as ConduitError with proper associated values, or informative error messages.

    LocalizedError

    By having ConduitError conform to the LocalizedError protocol, we can get descriptive error messages while debugging the application, or on application log output. Most importantly, we can get useful error messages printed out to the log when unit or integration test fail.

    For unit and integration tests, this can be easily achieved by passing the error to XCTFail as XCTFail(error.localizedDescription) or by having a test method throw. In both cases, XCTest will print useful information about the error, provided by Conduit.

    Before:

    Conduit.RequestSerializerError error 1.

    Now:

    Conduit Internal Error: Unnaceptable Mime type: text/html

    SessionTaskResponse

    When working with network requests, more than often, Conduit logic has to pass around information regarding Data, HTTPURLResponse and Error. This is often passed returned or passed around as a tuple of two or three elements. If we were to need the original URLRequest object, this tuple could now have four elements. While this technique works, it adds noise polluting the code.

    There was already an instance where Conduit stored this information in a TaskResponse structure, used for progressive downloads. This pull request takes advantage of this type and, while renaming it to SessionTaskResponse, uses it in all scenarios where the (data, response, error) tuple was previously used.

    By doing this, we remove boilerplate and simplify the code.

    For consuming applications/frameworks, calls like:

    client.begin(request: request) { (data, response, error) in
        if let error = error {
            ...
    }
    

    Will become:

    client.begin(request: request) { taskResponse in
        if let error = taskResponse.error {
            ...
    }
    

    Implementation

    • ConduitError was put together by removing all other Error types, while maintaining any relevant error cases. Error cases that were not widely used (eg. XMLError.notFound) were replaced with an internalError given a descriptive error message, and are no longer available.
    • LocalizedError was implemented to provide descriptive error messages for each supported error case.
    • SessionTaskResponse did already exist as TaskResponse. It was minimally refactored, and renamed. It is now used instead of the tuple (data, response, error).

    Test Plan

    There is no logic or behavior changes in this pull request. Conduit should work exactly as it did before. However, the interface for URLSessionClient requests, and for error pattern matching, will have to be updated by consuming applications/frameworks.

    Unit tests have been added for ConduitError to test pattern matching and debug descriptions.

    enhancement 
    opened by eneko 4
  • Fix log levels

    Fix log levels

    This pull request includes (pick all that apply):

    • [x] Bugfixes
    • [ ] New features
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [ ] Other

    Summary

    Log levels are currently being ignored due to a bug on the comparison between levels. This pull requests fixes the bug.

    In addition, log levels are reversed for easier comparison. Higher level == more verbose.

    No API changes, so this should be an easy patch release.

    release 
    opened by eneko 3
  • (Maint)|Use Swift.Result instead of custom implementation

    (Maint)|Use Swift.Result instead of custom implementation

    This pull request includes (pick all that apply):

    • [ ] Bugfixes
    • [ ] New features
    • [x] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [ ] Other

    Summary

    • Remove custom implementation of Result in favor of Swift's own implementation.

    Implementation

    • Remove Result
    • Extend Swift.Result with Block type alias and optional value/error getters.

    Test Plan

    • Run all tests
    enhancement 
    opened by eneko 2
  • Fix build error caused by invalid SwiftLint rule

    Fix build error caused by invalid SwiftLint rule

    This pull request includes (pick all that apply):

    • [x] Bugfixes
    • [ ] New features
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [ ] Other

    Summary

    duplicate_enum_cases rule was still failing when building Conduit dependency via Carthage. Adding it to .swiftlint.yml fixes the issue.

    opened by eneko 2
  • Fix warning. Partially enable treat-warnings-as-errors

    Fix warning. Partially enable treat-warnings-as-errors

    This pull request includes (pick all that apply):

    • [ ] Bugfixes
    • [ ] New features
    • [ ] Breaking changes
    • [ ] Documentation updates
    • [ ] Unit tests
    • [x] Other

    Summary

    • In leu of getting rid of all warnings, this PR fixes the only one remaining warning other than deprecation notices.
    • In addition, "treat-warnings-as-errors" has been turned on for ObjC.

    Implementation

    Move middleware code to separate method to reduce complexity.

    Test Plan

    • Run all tests
    enhancement 
    opened by eneko 2
  • Conduit > 0.11.0 not available on Cocoapods

    Conduit > 0.11.0 not available on Cocoapods

    Brief Description

    It appears the Cocoapods specs repo has not been updated for versions of Conduit above 0.11.0

    $ pod trunk info Conduit
    Conduit
        - Versions:
          - 0.10.0 (2018-06-18 23:36:55 UTC)
          - 0.10.1 (2018-06-19 23:02:04 UTC)
          - 0.10.2 (2018-07-03 21:56:29 UTC)
          - 0.10.3 (2018-07-06 20:52:22 UTC)
          - 0.11.0 (2018-07-18 23:27:57 UTC)
          - 0.6.0 (2017-10-18 03:43:33 UTC)
          - 0.6.1 (2017-10-24 00:38:11 UTC)
          - 0.7.0 (2017-11-09 00:09:58 UTC)
          - 0.9.0 (2018-05-16 21:37:14 UTC)
          - 0.9.1 (2018-05-22 23:28:24 UTC)
          - 0.9.2 (2018-06-07 17:05:20 UTC)
        - Owners:
          - John Hammerlund <[email protected]>
    
    opened by matt-holden 2
Releases(1.4.0)
  • 1.4.0(Oct 25, 2022)

  • 1.3.0(Mar 22, 2022)

  • 1.2.0(Aug 6, 2021)

  • 1.1.1(Jul 8, 2021)

  • 1.1.0(Jul 1, 2021)

    Breaking

    • None

    Enhancements

    • #162 Allow passing a custom operation queue to ImageDownloader to be used when calling the completion callback.
    • #161 Make ImageDownloader conform to ImageDownloaderType to allow for mocking.

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Apr 21, 2021)

    Breaking

    • Remove Xcode project and worskpaces, dropping support for Carthage

    Other

    • Celebrating Conduit's maturity with our 1.0.0 release 🎉
    Source code(tar.gz)
    Source code(zip)
  • 0.22.0(Oct 12, 2020)

    Breaking

    • Remove deprecated types: BearerOAuth2Token, OAuth2TokenAES256CBCCipher, and OAuth2TokenDiskStore

    Enhancements

    • None

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
  • 0.21.0(Apr 6, 2020)

    Breaking

    • ResponsePipelineMiddleware protocol has been updated.

    Enhancements

    • Surface request metrics to response middleware
      • SessionDelegate has been updated to capture request metrics in TaskResponse.
      • URLSessionClient has been updated to pass TaskResponse to any response middleware.
      • ResponsePipelineMiddleware has been refactored to pass a TaskResponse structure.

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
  • 0.20.0(Mar 27, 2020)

    Breaking

    • None

    Enhancements

    • Add TokenMigrator to perform migrations between token stores and client configurations.
    • Add ConduitDynamic dynamic library to Package description.

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
  • 0.19.0(Dec 23, 2019)

    Breaking

    • AES256CBCCipher.Error has been completely removed in favor of CryptoError

    Enhancements

    • Encryptor and Decryptor protocols have been added to genericize crypto operations
    • Cipher is a typealias for a type that is both an Encryptor and Decryptor
    • AES256CBCCipher now implements Cipher
    • HybridCipher has been added to support hybrid encryption, which delegates asymmetric key generation to a HybridKeyProvider
    • KeychainHybridKeyProvider uses keychain queries to provide either RSA or ECC key pairs. ECC keys are stored on the Secure Enclave if possible. More details here.
    • OAuth2TokenCryptoCipher delegates token data encryption / decryption to an underlying Encryptor and Decryptor
    • OAuth2TokenAES256CBCCipher has been deprecated in favor of OAuth2TokenCryptoCipher provided with an AES256CBCCipher

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
    Conduit.framework.zip(31.17 MB)
  • 0.18.3(Sep 23, 2019)

  • 0.18.2(Jul 25, 2019)

  • 0.18.1(Apr 19, 2019)

  • 0.18.0(Apr 3, 2019)

  • 0.17.0(Feb 28, 2019)

    Breaking

    • OAuth2TokenUserDefaultsStore conformance to OAuth2TokenEncryptedStore.
    • OAuth2TokenFileStore conformance to OAuth2TokenEncryptedStore.
    • OAuth2Authorization conformance to Equatable.
    • OAuth2Authorization now exposes read-only type and level properties.
    • OAuth2ClientConfiguration conformance to Equatable.
    • OAuth2ServerEnvironment conformance to Equatable.
    • OAuth2TokenStore protocol now exposes isRefreshTokenLockedFor, tokenIdentifierFor and tokenLockIdentifierFor.
    • BearerToken conformance to Equatable.
    • Add dependency to Security.framework

    Enhancements

    • Introduce OAuth2TokenCipher and OAuth2TokenEncryptedStore protocols to allow for token encryption/decryption.
    • User Defaults token store now supports token encryption.
    • File token store now supports token encryption.
    • Fully support application-side custom token stores.
    • Introduce OAuth2TokenAES256CBCCipher cipher for AES 256bit CBC token encryption.

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
    Conduit.framework.zip(27.46 MB)
  • 0.16.0(Feb 4, 2019)

  • 0.15.2(Jan 23, 2019)

  • 0.15.1(Oct 9, 2018)

  • 0.15.0(Sep 26, 2018)

  • 0.14.0(Sep 19, 2018)

    Breaking

    • serialize(request:bodyParameters:) is now public since FormEncodedRequestSerializer is a final class.
    • defaultHTTPHeaders is now public since static properties cannot be open.
    • Add XMLNodeAttributes to preserve order of attributes on serialized XML nodes

    Enhancements

    • Add new xmlString(format:) method to XML and XMLNode. XMLSerialization format options are:
      • .condensed -> same single-line condensed output as before.
      • .prettyPrinted(spaces: Int) -> human-readable format with flexible indentation level (number of spaces).

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
    Conduit.framework.zip(22.82 MB)
  • 0.13.0(Sep 5, 2018)

  • 0.12.0(Aug 30, 2018)

  • 0.11.0(Jul 18, 2018)

  • 0.10.3(Jul 6, 2018)

  • 0.10.2(Jul 3, 2018)

  • 0.10.1(Jun 19, 2018)

  • 0.10.0(Jun 18, 2018)

    0.10.0

    Breaking

    • OAuth2TokenStore now includes required interface for handling refresh token locks

    Enhancements

    • Loose-IPC is now used to handle a single active session across multiple processes (i.e. app extensions). Token refreshes were previously only safeguarded via serial pipeline; now, they are also protected against concurrent refreshes from other processes using the same storage
    • Precise token lock expiration control is available via OAuth2RequestPipelineMiddleware.tokenRefreshLockRelinquishInterval
    • OAuth2TokenUserDefaultsStore adds the ability to store to user-defined UserDefaults, most commonly for app group containers
    • OAuth2TokenFileStore adds additional I/O control, such as multiprocess file coordination via NSFileCoordinator and file protection

    Bug Fixes

    • OAuth2TokenFileStore solves a design flaw in OAuth2TokenDiskStore that prevented multiple tokens to be written for a single OAuth 2.0 client

    Other

    • Code coverage is now enforced via codecov.io
    • Added XMLRequestSerializerTests
    • Added AuthTokenMigratorTests
    • OAuth2TokenDiskStore is now deprecated in favor of OAuth2TokenFileStore and OAuth2TokenUserDefaultsStore
    Source code(tar.gz)
    Source code(zip)
    Conduit.framework.zip(14.64 MB)
  • 0.9.2(Jun 7, 2018)

  • 0.9.1(May 22, 2018)

  • 0.9.0(May 14, 2018)

    Breaking

    • Update XMLNode interface to better define usage expectations.
      • Default values for nodes(named:traversal:) and node() methods have been removed and traversal algorithm must be now set explicitly.
      • getValue(name:) has been updated to always use .firstLevel only.
      • New method findValue(name:traversal:) has been added, and requires the traversal algorithm to be set explicitly.

    Enhancements

    • None

    Bug Fixes

    • None

    Other

    • None
    Source code(tar.gz)
    Source code(zip)
    Conduit.framework.zip(16.38 MB)
Owner
Mindbody
Mindbody
🌐 Makes Internet connectivity detection more robust by detecting Wi-Fi networks without Internet access.

Connectivity is a wrapper for Apple's Reachability providing a reliable measure of whether Internet connectivity is available where Reachability alone

Ross Butler 1.6k Dec 30, 2022
OctopusKit is a simplicity but graceful solution for invoke RESTful web service APIs

OctopusKit OctopusKit is a simplicity but graceful solution for invoke RESTful web service APIs,it can help coder develop app based MVC pattern, it ca

i ⚒  kit 3 Jul 29, 2021
A Swift Multiplatform Single-threaded Non-blocking Web and Networking Framework

Serverside non-blocking IO in Swift Ask questions in our Slack channel! Lightning (formerly Edge) Node Lightning is an HTTP Server and TCP Client/Serv

SkyLab 316 Oct 6, 2022
Klab Academy, example of calling APIs in flutter

klab A new Flutter application. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

Hubert IT 0 Dec 2, 2021
Patchman - A macOS application to test APIs with HTTP methods (Decluttered Postman)

Patchman A macOS application to test APIs with HTTP methods (Decluttered Postman

Praneet 148 Nov 25, 2022
Elegant HTTP Networking in Swift

Alamofire is an HTTP networking library written in Swift. Features Component Libraries Requirements Migration Guides Communication Installation Usage

Alamofire 38.7k Jan 8, 2023
Versatile HTTP Networking in Swift

Net is a versatile HTTP networking library written in Swift. ?? Features URL / JSON / Property List Parameter Encoding Upload File / Data / Stream / M

Intelygenz 124 Dec 6, 2022
A type-safe, high-level networking solution for Swift apps

What Type-safe network calls made easy Netswift offers an easy way to perform network calls in a structured and type-safe way. Why Networking in Swift

Dorian Grolaux 23 Apr 27, 2022
🏇 A Swift HTTP / HTTPS networking library just incidentally execute on machines

Thus, programs must be written for people to read, and only incidentally for machines to execute. Harold Abelson, "Structure and Interpretation of Com

John Lui 845 Oct 30, 2022
Simple asynchronous HTTP networking class for Swift

YYHRequest YYHRequest is a simple and lightweight class for loading asynchronous HTTP requests in Swift. Built on NSURLConnection and NSOperationQueue

yayuhh 77 May 18, 2022
RSNetworking is a networking library written entirly for the Swift programming language.

RSNetworking is a networking library written entirly for the Swift programming language.

null 18 Feb 25, 2018
ServiceData is an HTTP networking library written in Swift which can download different types of data.

ServiceData Package Description : ServiceData is an HTTP networking library written in Swift which can download different types of data. Features List

Mubarak Alseif 0 Nov 11, 2021
Easy HTTP Networking in Swift a NSURLSession wrapper with image caching support

Networking was born out of the necessity of having a simple networking library that doesn't have crazy programming abstractions or uses the latest rea

Nes 1.3k Dec 17, 2022
A networking library for Swift

Nikka Nikka is a super simple Swift HTTP networking library that comes with many extensions to make it modular and really powerful. Installation Usage

Emilien Stremsdoerfer 29 Nov 4, 2022
Declarative and Reactive Networking for Swift.

Squid Squid is a declarative and reactive networking library for Swift. Developed for Swift 5, it aims to make use of the latest language features. Th

Oliver Borchert 69 Dec 18, 2022
AsyncHTTP - Generic networking library written using Swift async/await

Generic networking library written using Swift async/await

Laszlo Teveli 7 Aug 3, 2022
A lightweight generic networking API written purely in Swift

SwiftyNetworking SwiftyNetworking library is a generic networking library writte

Zaid Rahhawi 0 Dec 24, 2021
Swift implementation of libp2p, a modular & extensible networking stack

Swift LibP2P The Swift implementation of the libp2p networking stack Table of Contents Overview Disclaimer Install Usage Example API Contributing Cred

null 19 Dec 18, 2022
Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

Yosuke Ishikawa 1.9k Dec 30, 2022