TF-2384 Create TwakeCore in ios folder to handle network jmap request to server
Signed-off-by: dab246 <tdvu@linagora.com> (cherry picked from commit d8a330b987860f133b7e56b4aa67669c9d3c8f20)
This commit is contained in:
@@ -63,7 +63,7 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func didSelectPost() {
|
override func didSelectPost() {
|
||||||
print("didSelectPost");
|
TwakeLogger.shared.log(message: "didSelectPost");
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configurationItems() -> [Any]! {
|
override func configurationItems() -> [Any]! {
|
||||||
@@ -204,7 +204,7 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
|
|
||||||
|
|
||||||
private func dismissWithError() {
|
private func dismissWithError() {
|
||||||
print("[ERROR] Error loading data!")
|
TwakeLogger.shared.log(message: "[ERROR] Error loading data!")
|
||||||
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
|
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
|
||||||
|
|
||||||
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
|
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
|
||||||
@@ -275,7 +275,7 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
}
|
}
|
||||||
try FileManager.default.copyItem(at: srcURL, to: dstURL)
|
try FileManager.default.copyItem(at: srcURL, to: dstURL)
|
||||||
} catch (let error) {
|
} catch (let error) {
|
||||||
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
|
TwakeLogger.shared.log(message: "Cannot copy item at \(srcURL) to \(dstURL): \(error)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -331,7 +331,7 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
|
|
||||||
// Debug method to print out SharedMediaFile details in the console
|
// Debug method to print out SharedMediaFile details in the console
|
||||||
func toString() {
|
func toString() {
|
||||||
print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
|
TwakeLogger.shared.log(message: "[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class JmapExceptions: Error, Equatable {
|
||||||
|
static func == (value1: JmapExceptions, value2: JmapExceptions) -> Bool {
|
||||||
|
return value1.value == value2.value
|
||||||
|
}
|
||||||
|
|
||||||
|
static let notFoundNewEmails = JmapExceptions(value: "Not found news emails")
|
||||||
|
|
||||||
|
var value: String?
|
||||||
|
|
||||||
|
init(value: String? = nil) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class NetworkExceptions: Error {
|
||||||
|
static let requestUrlInvalid = NetworkExceptions(value: "Request url invalid")
|
||||||
|
|
||||||
|
var value: String?
|
||||||
|
|
||||||
|
init(value: String? = nil) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import Foundation
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
class JmapClient {
|
||||||
|
static let shared: JmapClient = JmapClient()
|
||||||
|
|
||||||
|
private let jmapHeader = HTTPHeader(name: "Accept", value: "application/json;jmapVersion=rfc-8621")
|
||||||
|
|
||||||
|
private func getBasicAuthorization(basicAuth: String) -> String {
|
||||||
|
return "Basic \(basicAuth)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNewEmails(
|
||||||
|
apiUrl: String,
|
||||||
|
accountId: String,
|
||||||
|
sinceState: String,
|
||||||
|
authenticationType: AuthenticationType,
|
||||||
|
tokenOidc: TokenOidc?,
|
||||||
|
basicAuth: String?,
|
||||||
|
onSuccess: @escaping ([Email]) -> Void,
|
||||||
|
onFailure: @escaping (Error) -> Void
|
||||||
|
) {
|
||||||
|
let authenticationValue = authenticationType == AuthenticationType.basic
|
||||||
|
? getBasicAuthorization(basicAuth: basicAuth ?? "")
|
||||||
|
: tokenOidc?.getAuthorization() ?? ""
|
||||||
|
|
||||||
|
let headers = HTTPHeaders([
|
||||||
|
jmapHeader,
|
||||||
|
HTTPHeader(name: "Authorization", value: authenticationValue)
|
||||||
|
])
|
||||||
|
|
||||||
|
let jmapRequestObject = JmapRequestGenerator.shared.createEmailChangesRequest(
|
||||||
|
accountId: accountId,
|
||||||
|
sinceState: sinceState
|
||||||
|
)
|
||||||
|
|
||||||
|
AlamofireService.shared.post(
|
||||||
|
url: apiUrl,
|
||||||
|
payloadData: jmapRequestObject?.toData(),
|
||||||
|
headers: headers,
|
||||||
|
onSuccess: { (data: JmapResponseObject<Email>) in
|
||||||
|
if let listEmail = data.parsing(methodName: JmapConstants.EMAIL_GET_METHOD_NAME, methodCallId: "c1"), !listEmail.isEmpty {
|
||||||
|
onSuccess(listEmail)
|
||||||
|
} else {
|
||||||
|
onFailure(JmapExceptions.notFoundNewEmails)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure: onFailure
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Email: Codable {
|
||||||
|
let id: String
|
||||||
|
let subject: String?
|
||||||
|
let preview: String?
|
||||||
|
let from: [EmailAddress]?
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct EmailAddress: Codable {
|
||||||
|
let name: String?
|
||||||
|
let email: String?
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct JmapRequestObject: Codable {
|
||||||
|
let using: [String]
|
||||||
|
let methodCalls: [[RequestInvocation]]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RequestInvocation: Codable {
|
||||||
|
case methodRequest(MethodRequest)
|
||||||
|
case string(String)
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
if let x = try? container.decode(String.self) {
|
||||||
|
self = .string(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let x = try? container.decode(MethodRequest.self) {
|
||||||
|
self = .methodRequest(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw DecodingError.typeMismatch(RequestInvocation.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RequestInvocation"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
switch self {
|
||||||
|
case .methodRequest(let x):
|
||||||
|
try container.encode(x)
|
||||||
|
case .string(let x):
|
||||||
|
try container.encode(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MethodRequest: Codable {
|
||||||
|
let accountId: String
|
||||||
|
let sinceState: String?
|
||||||
|
let ids: ResultReference?
|
||||||
|
let properties: [String]?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case accountId
|
||||||
|
case sinceState
|
||||||
|
case ids = "#ids"
|
||||||
|
case properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ResultReference: Codable {
|
||||||
|
let resultOf, name, path: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JmapRequestObject {
|
||||||
|
func toData() -> Data? {
|
||||||
|
if let encodedData = try? JSONEncoder().encode(self) {
|
||||||
|
return encodedData
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJson() -> String? {
|
||||||
|
if let data = toData(), let jsonString = String(data: data, encoding: .utf8) {
|
||||||
|
return jsonString
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct JmapResponseObject<T: Codable>: Codable {
|
||||||
|
let sessionState: String
|
||||||
|
let methodResponses: [[ResponseInvocation<T>]]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ResponseInvocation<T: Codable>: Codable {
|
||||||
|
case methodResponse(MethodResponse<T>)
|
||||||
|
case string(String)
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
if let x = try? container.decode(String.self) {
|
||||||
|
self = .string(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let x = try? container.decode(MethodResponse<T>.self) {
|
||||||
|
self = .methodResponse(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw DecodingError.typeMismatch(ResponseInvocation<T>.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ResponseInvocation"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
switch self {
|
||||||
|
case .methodResponse(let x):
|
||||||
|
try container.encode(x)
|
||||||
|
case .string(let x):
|
||||||
|
try container.encode(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var listObject: [T]? {
|
||||||
|
switch self {
|
||||||
|
case .methodResponse(let method): return method.list
|
||||||
|
case .string(_): return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MethodResponse<T: Codable>: Codable {
|
||||||
|
let accountId: String
|
||||||
|
let oldState: String?
|
||||||
|
let newState: String?
|
||||||
|
let hasMoreChanges: Bool?
|
||||||
|
let created: [String]?
|
||||||
|
let updated: [String]?
|
||||||
|
let destroyed: [String]?
|
||||||
|
let notFound: [String]?
|
||||||
|
let state: String?
|
||||||
|
let list: [T]?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JmapResponseObject {
|
||||||
|
func parsing(methodName: String, methodCallId: String) -> [T]? {
|
||||||
|
let invocations = methodResponses.first { (responseInvocations: [ResponseInvocation<T>]) in
|
||||||
|
return responseInvocations.contains { (responseInvocation: ResponseInvocation<T>) in
|
||||||
|
return validateResponseInvocation(response: responseInvocation, methodName: methodName, methodCallId: methodCallId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if invocations == nil || invocations?.isEmpty == true {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
let invocation = invocations!.first { response in
|
||||||
|
return validateMethodResponse(response: response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invocation?.listObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateResponseInvocation(response: ResponseInvocation<T>, methodName: String, methodCallId: String) -> Bool {
|
||||||
|
switch response {
|
||||||
|
case .string(let stringValue):
|
||||||
|
return stringValue == methodName || stringValue == methodCallId
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateMethodResponse(response: ResponseInvocation<T>) -> Bool {
|
||||||
|
switch response {
|
||||||
|
case .methodResponse(_):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class JmapConstants {
|
||||||
|
static let EMAIL_GET_METHOD_NAME = "Email/get"
|
||||||
|
static let EMAIL_CHANGES_METHOD_NAME = "Email/changes"
|
||||||
|
|
||||||
|
static let CREATED_REFERENCE_PATH = "/created/*"
|
||||||
|
|
||||||
|
static let JMAP_CORE_CAPABILITY = "urn:ietf:params:jmap:core"
|
||||||
|
static let JMAP_MAIL_CAPABILITY = "urn:ietf:params:jmap:mail"
|
||||||
|
|
||||||
|
static let EMAIL_PUSH_NOTIFICATION_PROPERTIES = [
|
||||||
|
"id",
|
||||||
|
"subject",
|
||||||
|
"preview",
|
||||||
|
"from"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class JmapRequestGenerator {
|
||||||
|
static let shared: JmapRequestGenerator = JmapRequestGenerator()
|
||||||
|
|
||||||
|
func createEmailChangesRequest(accountId: String, sinceState: String) -> JmapRequestObject? {
|
||||||
|
return JmapRequestObject(
|
||||||
|
using: [
|
||||||
|
JmapConstants.JMAP_CORE_CAPABILITY,
|
||||||
|
JmapConstants.JMAP_MAIL_CAPABILITY
|
||||||
|
],
|
||||||
|
methodCalls: [
|
||||||
|
[
|
||||||
|
RequestInvocation.string(JmapConstants.EMAIL_CHANGES_METHOD_NAME),
|
||||||
|
RequestInvocation.methodRequest(
|
||||||
|
MethodRequest(
|
||||||
|
accountId: accountId,
|
||||||
|
sinceState: sinceState,
|
||||||
|
ids: nil,
|
||||||
|
properties: nil
|
||||||
|
)
|
||||||
|
),
|
||||||
|
RequestInvocation.string("c0"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
RequestInvocation.string(JmapConstants.EMAIL_GET_METHOD_NAME),
|
||||||
|
RequestInvocation.methodRequest(
|
||||||
|
MethodRequest(
|
||||||
|
accountId: accountId,
|
||||||
|
sinceState: nil,
|
||||||
|
ids: ResultReference(
|
||||||
|
resultOf: "c0",
|
||||||
|
name: JmapConstants.EMAIL_CHANGES_METHOD_NAME,
|
||||||
|
path: JmapConstants.CREATED_REFERENCE_PATH
|
||||||
|
),
|
||||||
|
properties: JmapConstants.EMAIL_PUSH_NOTIFICATION_PROPERTIES
|
||||||
|
)
|
||||||
|
),
|
||||||
|
RequestInvocation.string("c1"),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class TwakeLogger {
|
||||||
|
static let shared: TwakeLogger = TwakeLogger()
|
||||||
|
|
||||||
|
func log(message: String) {
|
||||||
|
debugPrint(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import Foundation
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
class AlamofireService {
|
||||||
|
static let shared: AlamofireService = AlamofireService()
|
||||||
|
|
||||||
|
func post<T: Codable>(
|
||||||
|
url: String,
|
||||||
|
payloadData: Data?,
|
||||||
|
headers: HTTPHeaders? = nil,
|
||||||
|
onSuccess: @escaping (T) -> Void,
|
||||||
|
onFailure: @escaping (Error) -> Void
|
||||||
|
) {
|
||||||
|
guard let baseUrl = URL(string: url),
|
||||||
|
var request = try? URLRequest(url: baseUrl, method: .post, headers: headers) else {
|
||||||
|
return onFailure(NetworkExceptions.requestUrlInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.httpBody = payloadData
|
||||||
|
|
||||||
|
AF.request(request).responseDecodable(of: T.self) { response in
|
||||||
|
switch(response.result) {
|
||||||
|
case .success(let data):
|
||||||
|
onSuccess(data)
|
||||||
|
case .failure(let error):
|
||||||
|
onFailure(NetworkExceptions(value: "\(error.localizedDescription)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum AuthenticationType: String, Codable {
|
||||||
|
case basic, oidc, none
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct TokenOidc: Codable {
|
||||||
|
let token: String
|
||||||
|
let tokenId: String
|
||||||
|
let expiredTime: String?
|
||||||
|
let refreshToken: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TokenOidc {
|
||||||
|
func getAuthorization() -> String {
|
||||||
|
return "Bearer \(token)"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user