Dactyloidae/mobile/ios/Push/PushClient.swift
2026-06-26 21:04:09 -07:00

187 lines
7 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import Alamofire
import Deferred
import Shared
import SwiftyJSON
//public struct PushRemoteError {
// static let MissingNecessaryCryptoKeys: Int32 = 101
// static let InvalidURLEndpoint: Int32 = 102
// static let ExpiredURLEndpoint: Int32 = 103
// static let DataPayloadTooLarge: Int32 = 104
// static let EndpointBecameUnavailable: Int32 = 105
// static let InvalidSubscription: Int32 = 106
// static let RouterTypeIsInvalid: Int32 = 108
// static let InvalidAuthentication: Int32 = 109
// static let InvalidCryptoKeysSpecified: Int32 = 110
// static let MissingRequiredHeader: Int32 = 111
// static let InvalidTTLHeaderValue: Int32 = 112
// static let UnknownError: Int32 = 999
//}
public let PushClientErrorDomain = "org.mozilla.push.error"
private let PushClientUnknownError = NSError(domain: PushClientErrorDomain, code: 999,
userInfo: [NSLocalizedDescriptionKey: "Invalid server response"])
private let log = Logger.browserLogger
/// Bug 1364403  This is to be put into the push registration
private let apsEnvironment: [String: Any] = [
"mutable-content": 1,
"alert": [
"title": " ",
"body": " "
],
]
public struct PushRemoteError {
let code: Int
let errno: Int
let error: String
let message: String?
public static func from(json: JSON) -> PushRemoteError? {
guard let code = json["code"].int,
let errno = json["errno"].int,
let error = json["error"].string else {
return nil
}
let message = json["message"].string
return PushRemoteError(code: code, errno: errno, error: error, message: message)
}
}
public enum PushClientError: MaybeErrorType {
case Remote(PushRemoteError)
case Local(Error)
public var description: String {
switch self {
case let .Remote(error):
let errorString = error.error
let messageString = error.message ?? ""
return "<FxAClientError.Remote \(error.code)/\(error.errno): \(errorString) (\(messageString))>"
case let .Local(error):
return "<FxAClientError.Local Error \"\(error.localizedDescription)\">"
}
}
}
public class PushClient {
let endpointURL: NSURL
let experimentalMode: Bool
lazy fileprivate var alamofire: SessionManager = {
let ua = UserAgent.fxaUserAgent
let configuration = URLSessionConfiguration.ephemeral
var defaultHeaders = SessionManager.default.session.configuration.httpAdditionalHeaders ?? [:]
defaultHeaders["User-Agent"] = ua
configuration.httpAdditionalHeaders = defaultHeaders
return SessionManager(configuration: configuration)
}()
public init(endpointURL: NSURL, experimentalMode: Bool = false) {
self.endpointURL = endpointURL
self.experimentalMode = experimentalMode
}
}
public extension PushClient {
public func register(_ apnsToken: String) -> Deferred<Maybe<PushRegistration>> {
// POST /v1/{type}/{app_id}/registration
let registerURL = endpointURL.appendingPathComponent("registration")!
var mutableURLRequest = URLRequest(url: registerURL)
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
let parameters: [String: Any]
if experimentalMode {
parameters = [
"token": apnsToken,
"aps": apsEnvironment,
]
} else {
parameters = ["token": apnsToken]
}
mutableURLRequest.httpBody = JSON(parameters).stringValue()?.utf8EncodedData
if experimentalMode {
log.info("curl -X POST \(registerURL.absoluteString) --data '\(JSON(parameters).stringValue()!)'")
}
return send(request: mutableURLRequest) >>== { json in
guard let response = PushRegistration.from(json: json) else {
return deferMaybe(PushClientError.Local(PushClientUnknownError))
}
return deferMaybe(response)
}
}
public func updateUAID(_ apnsToken: String, withRegistration creds: PushRegistration) -> Deferred<Maybe<PushRegistration>> {
// PUT /v1/{type}/{app_id}/registration/{uaid}
let registerURL = endpointURL.appendingPathComponent("registration/\(creds.uaid)")!
var mutableURLRequest = URLRequest(url: registerURL)
mutableURLRequest.httpMethod = HTTPMethod.put.rawValue
mutableURLRequest.addValue("Bearer \(creds.secret)", forHTTPHeaderField: "Authorization")
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
let parameters = ["token": apnsToken]
mutableURLRequest.httpBody = JSON(parameters).stringValue()?.utf8EncodedData
return send(request: mutableURLRequest) >>== { json in
return deferMaybe(creds)
}
}
public func unregister(_ creds: PushRegistration) -> Success {
// DELETE /v1/{type}/{app_id}/registration/{uaid}
let unregisterURL = endpointURL.appendingPathComponent("registration/\(creds.uaid)")
var mutableURLRequest = URLRequest(url: unregisterURL!)
mutableURLRequest.httpMethod = HTTPMethod.delete.rawValue
mutableURLRequest.addValue("Bearer \(creds.secret)", forHTTPHeaderField: "Authorization")
return send(request: mutableURLRequest) >>> succeed
}
}
/// Utilities
extension PushClient {
fileprivate func send(request: URLRequest) -> Deferred<Maybe<JSON>> {
log.info("\(request.httpMethod!) \(request.url?.absoluteString ?? "nil")")
let deferred = Deferred<Maybe<JSON>>()
alamofire.request(request)
.validate(contentType: ["application/json"])
.responseJSON { response in
// Don't cancel requests just because our client is deallocated.
withExtendedLifetime(self.alamofire) {
let result = response.result
if let error = result.error {
return deferred.fill(Maybe(failure: PushClientError.Local(error)))
}
guard let data = response.data else {
return deferred.fill(Maybe(failure: PushClientError.Local(PushClientUnknownError)))
}
let json = JSON(data: data)
if let remoteError = PushRemoteError.from(json: json) {
return deferred.fill(Maybe(failure: PushClientError.Remote(remoteError)))
}
deferred.fill(Maybe(success: json))
}
}
return deferred
}
}