diff --git a/swift-sdk/Constants.swift b/swift-sdk/Constants.swift index 0e51a6e25..f74259141 100644 --- a/swift-sdk/Constants.swift +++ b/swift-sdk/Constants.swift @@ -19,6 +19,8 @@ enum Const { enum Http { static let GET = "GET" static let POST = "POST" + static let PATCH = "PATCH" + static let DELETE = "DELETE" } enum Path { @@ -39,6 +41,7 @@ enum Const { static let updateEmail = "users/updateEmail" static let updateSubscriptions = "users/updateSubscriptions" static let getRemoteConfiguration = "mobile/getRemoteConfiguration" + static let subscriptions = "subscriptions/" } public enum UserDefault { diff --git a/swift-sdk/Internal/ApiClient.swift b/swift-sdk/Internal/ApiClient.swift index 104aac9bb..2e884f8ce 100644 --- a/swift-sdk/Internal/ApiClient.swift +++ b/swift-sdk/Internal/ApiClient.swift @@ -216,4 +216,14 @@ extension ApiClient: ApiClientProtocol { let result = createRequestCreator().flatMap { $0.createGetRemoteConfigurationRequest() } return send(iterableRequestResult: result) } + + func subscribeUser(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Pending { + let result = createRequestCreator().flatMap { $0.createSubscribeUserRequest(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup) } + return send(iterableRequestResult: result) + } + + func unSubscribeUser(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Pending { + let result = createRequestCreator().flatMap { $0.createUnSubscribeUserRequest(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup) } + return send(iterableRequestResult: result) + } } diff --git a/swift-sdk/Internal/ApiClientProtocol.swift b/swift-sdk/Internal/ApiClientProtocol.swift index 86a57812e..8e2817864 100644 --- a/swift-sdk/Internal/ApiClientProtocol.swift +++ b/swift-sdk/Internal/ApiClientProtocol.swift @@ -45,4 +45,8 @@ protocol ApiClientProtocol: AnyObject { func disableDevice(forAllUsers allUsers: Bool, hexToken: String) -> Pending func getRemoteConfiguration() -> Pending + + @discardableResult func subscribeUser(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Pending + + @discardableResult func unSubscribeUser(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Pending } diff --git a/swift-sdk/Internal/InternalIterableAPI.swift b/swift-sdk/Internal/InternalIterableAPI.swift index 3f2e0f454..cb46a31e7 100644 --- a/swift-sdk/Internal/InternalIterableAPI.swift +++ b/swift-sdk/Internal/InternalIterableAPI.swift @@ -407,6 +407,26 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { onFailure: onFailure) } + @discardableResult + func subscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) -> Pending { + requestHandler.subscribeUser(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup, onSuccess: onSuccess, onFailure: onFailure) + } + + @discardableResult + func unSubscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) -> Pending { + requestHandler.unSubscribeUser(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup, onSuccess: onSuccess, onFailure: onFailure) + } + // MARK: - Private/Internal private var config: IterableConfig diff --git a/swift-sdk/Internal/IterableAPICallRequest.swift b/swift-sdk/Internal/IterableAPICallRequest.swift index 640f6bc84..b7675df70 100644 --- a/swift-sdk/Internal/IterableAPICallRequest.swift +++ b/swift-sdk/Internal/IterableAPICallRequest.swift @@ -29,10 +29,20 @@ struct IterableAPICallRequest { case let .post(postRequest): return IterableRequestUtil.createPostRequest(forApiEndPoint: endpoint, path: postRequest.path, - headers: createIterableHeaders(sentAt: sentAt, - processorType: processorType), + headers: createIterableHeaders(sentAt: sentAt, processorType: processorType), args: postRequest.args, body: postRequest.body) + case let .patch(patchRequest): + return IterableRequestUtil.createPatchRequest(forApiEndPoint: endpoint, + path: patchRequest.path, + headers: createIterableHeaders(sentAt: sentAt, processorType: processorType), + args: patchRequest.args) + case let .delete(deleteRequest): + return IterableRequestUtil.createDeleteRequest(forApiEndPoint: endpoint, + path: deleteRequest.path, + headers: createIterableHeaders(sentAt: sentAt, + processorType: processorType), + args: deleteRequest.args) } } @@ -42,6 +52,10 @@ struct IterableAPICallRequest { return request.path case .post(let request): return request.path + case .patch(let request): + return request.path + case .delete(let request): + return request.path } } diff --git a/swift-sdk/Internal/IterableRequest.swift b/swift-sdk/Internal/IterableRequest.swift index 9b33019ca..ec5f9ed2d 100644 --- a/swift-sdk/Internal/IterableRequest.swift +++ b/swift-sdk/Internal/IterableRequest.swift @@ -10,6 +10,8 @@ import Foundation enum IterableRequest { case get(GetRequest) case post(PostRequest) + case patch(PatchRequest) + case delete(DeleteRequest) } extension IterableRequest: Codable { @@ -25,9 +27,15 @@ extension IterableRequest: Codable { case IterableRequest.requestTypeGet: let request = try container.decode(GetRequest.self, forKey: .value) self = .get(request) + case IterableRequest.requestTypePatch: + let request = try container.decode(PatchRequest.self, forKey: .value) + self = .patch(request) case IterableRequest.requestTypePost: let request = try container.decode(PostRequest.self, forKey: .value) self = .post(request) + case IterableRequest.requestTypeDelete: + let request = try container.decode(DeleteRequest.self, forKey: .value) + self = .delete(request) default: throw IterableError.general(description: "Unknown request type: \(type)") } @@ -39,9 +47,15 @@ extension IterableRequest: Codable { case let .get(request): try container.encode(IterableRequest.requestTypeGet, forKey: .type) try container.encode(request, forKey: .value) + case let .patch(request): + try container.encode(IterableRequest.requestTypePatch, forKey: .type) + try container.encode(request, forKey: .value) case let .post(request): try container.encode(IterableRequest.requestTypePost, forKey: .type) try container.encode(request, forKey: .value) + case let .delete(request): + try container.encode(IterableRequest.requestTypeDelete, forKey: .type) + try container.encode(request, forKey: .value) } } @@ -54,7 +68,9 @@ extension IterableRequest: Codable { } private static let requestTypeGet = "get" + private static let requestTypePatch = "patch" private static let requestTypePost = "post" + private static let requestTypeDelete = "delete" } struct GetRequest: Codable { @@ -62,6 +78,16 @@ struct GetRequest: Codable { let args: [String: String]? } +struct PatchRequest: Codable { + let path: String + let args: [String: String]? +} + +struct DeleteRequest: Codable { + let path: String + let args: [String: String]? +} + struct PostRequest { let path: String let args: [String: String]? diff --git a/swift-sdk/Internal/IterableRequestUtil.swift b/swift-sdk/Internal/IterableRequestUtil.swift index 2a6ba98cb..0219bdd53 100644 --- a/swift-sdk/Internal/IterableRequestUtil.swift +++ b/swift-sdk/Internal/IterableRequestUtil.swift @@ -23,6 +23,26 @@ struct IterableRequestUtil { return request } + static func createPatchRequest(forApiEndPoint apiEndPoint: String, + path: String, + headers: [String: String]? = nil, + args: [String: String]? = nil) -> URLRequest? { + createPatchRequest1(forApiEndPoint: apiEndPoint, + path: path, + headers: headers, + args: args) + } + + static func createDeleteRequest(forApiEndPoint apiEndPoint: String, + path: String, + headers: [String: String]? = nil, + args: [String: String]? = nil) -> URLRequest? { + createDeleteRequest1(forApiEndPoint: apiEndPoint, + path: path, + headers: headers, + args: args) + } + static func createPostRequest(forApiEndPoint apiEndPoint: String, path: String, headers: [String: String]? = nil, @@ -64,6 +84,37 @@ struct IterableRequestUtil { return request } + static func createPatchRequest1(forApiEndPoint apiEndPoint: String, + path: String, + headers: [String: String]? = nil, + args: [String: String]? = nil) -> URLRequest? { + guard let url = getUrlComponents(forApiEndPoint: apiEndPoint, path: path, args: args)?.url else { + return nil + } + + var request = URLRequest(url: url) + addHeaders(headers: headers, toRequest: &request) + request.httpMethod = Const.Http.PATCH + + return request + } + + static func createDeleteRequest1(forApiEndPoint apiEndPoint: String, + path: String, + headers: [String: String]? = nil, + args: [String: String]? = nil, + body: Data? = nil) -> URLRequest? { + guard let url = getUrlComponents(forApiEndPoint: apiEndPoint, path: path, args: args)?.url else { + return nil + } + + var request = URLRequest(url: url) + addHeaders(headers: headers, toRequest: &request) + request.httpMethod = Const.Http.DELETE + + return request + } + static func dictToJsonData(_ dict: [AnyHashable: Any]?) -> Data? { guard let dict = dict else { return nil diff --git a/swift-sdk/Internal/OnlineRequestProcessor.swift b/swift-sdk/Internal/OnlineRequestProcessor.swift index 0a51524d5..b611704f3 100644 --- a/swift-sdk/Internal/OnlineRequestProcessor.swift +++ b/swift-sdk/Internal/OnlineRequestProcessor.swift @@ -233,6 +233,32 @@ struct OnlineRequestProcessor: RequestProcessorProtocol { requestIdentifier: "inAppConsumeWithSource") } + @discardableResult + func subscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) -> Pending { + sendRequest(requestProvider: { apiClient.subscribeUser(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup)}, + successHandler: onSuccess, + failureHandler: onFailure, + requestIdentifier: "subscribeUser") + } + + @discardableResult + func unSubscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) -> Pending { + sendRequest(requestProvider: { apiClient.unSubscribeUser(_email: _email, userId: userId, subscriptionId: subscriptionId, subscriptionGroup: subscriptionGroup)}, + successHandler: onSuccess, + failureHandler: onFailure, + requestIdentifier: "unSubscribeUser") + } + func getRemoteConfiguration() -> Pending { apiClient.getRemoteConfiguration() } diff --git a/swift-sdk/Internal/RequestCreator.swift b/swift-sdk/Internal/RequestCreator.swift index c80f4a6fc..14b6d243d 100644 --- a/swift-sdk/Internal/RequestCreator.swift +++ b/swift-sdk/Internal/RequestCreator.swift @@ -442,6 +442,40 @@ struct RequestCreator { return .success(.get(createGetRequest(forPath: Const.Path.getRemoteConfiguration, withArgs: args as! [String: String]))) } + func createSubscribeUserRequest(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Result { + if case .none = auth.emailOrUserId { + ITBError(Self.authMissingMessage) + return .failure(IterableError.general(description: Self.authMissingMessage)) + } + + var endpoint: String + + if !userId!.isEmpty { + endpoint = Const.Path.subscriptions + subscriptionGroup + "/" + subscriptionId + "/byUserId/" + userId! + } else { + endpoint = Const.Path.subscriptions + subscriptionGroup + "/" + subscriptionId + "/user/" + _email + } + + return .success(.patch(createPatchRequest(forPath: endpoint, withArgs: [String: String]()))) + } + + func createUnSubscribeUserRequest(_email: String, userId: String?, subscriptionId: String, subscriptionGroup: String) -> Result { + if case .none = auth.emailOrUserId { + ITBError(Self.authMissingMessage) + return .failure(IterableError.general(description: Self.authMissingMessage)) + } + + var endpoint: String + + if !userId!.isEmpty { + endpoint = Const.Path.subscriptions + subscriptionGroup + "/" + subscriptionId + "/byUserId/" + userId! + } else { + endpoint = Const.Path.subscriptions + subscriptionGroup + "/" + subscriptionId + "/user/" + _email + } + + return .success(.delete(createDeleteRequest(forPath: endpoint, withArgs: [String: String]()))) + } + // MARK: - PRIVATE private static let authMissingMessage = "Both email and userId are nil" @@ -457,6 +491,16 @@ struct RequestCreator { args: args) } + private func createPatchRequest(forPath path: String, withArgs args: [String: String]?) -> PatchRequest { + PatchRequest(path: path, + args: args) + } + + private func createDeleteRequest(forPath path: String, withArgs args: [String: String]?) -> DeleteRequest { + DeleteRequest(path: path, + args: args) + } + private static func pushServicePlatformToString(_ pushServicePlatform: PushServicePlatform, apnsType: APNSType) -> String { switch pushServicePlatform { case .production: diff --git a/swift-sdk/Internal/RequestHandler.swift b/swift-sdk/Internal/RequestHandler.swift index f395b725a..1047b062f 100644 --- a/swift-sdk/Internal/RequestHandler.swift +++ b/swift-sdk/Internal/RequestHandler.swift @@ -257,6 +257,36 @@ class RequestHandler: RequestHandlerProtocol { } } + @discardableResult + func subscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler?, + onFailure: OnFailureHandler?) -> Pending { + onlineProcessor.subscribeUser(_email: _email, + userId: userId, + subscriptionId: subscriptionId, + subscriptionGroup: subscriptionGroup, + onSuccess: onSuccess, + onFailure: onFailure) + } + + @discardableResult + func unSubscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler?, + onFailure: OnFailureHandler?) -> Pending { + onlineProcessor.unSubscribeUser(_email: _email, + userId: userId, + subscriptionId: subscriptionId, + subscriptionGroup: subscriptionGroup, + onSuccess: onSuccess, + onFailure: onFailure) + } + func getRemoteConfiguration() -> Pending { onlineProcessor.getRemoteConfiguration() } diff --git a/swift-sdk/Internal/RequestHandlerProtocol.swift b/swift-sdk/Internal/RequestHandlerProtocol.swift index a3b370833..2f0f5da17 100644 --- a/swift-sdk/Internal/RequestHandlerProtocol.swift +++ b/swift-sdk/Internal/RequestHandlerProtocol.swift @@ -117,6 +117,22 @@ protocol RequestHandlerProtocol: AnyObject { inboxSessionId: String?, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) -> Pending + + @discardableResult + func subscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler?, + onFailure: OnFailureHandler?) -> Pending + + @discardableResult + func unSubscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler?, + onFailure: OnFailureHandler?) -> Pending func handleLogout() throws diff --git a/swift-sdk/IterableAPI.swift b/swift-sdk/IterableAPI.swift index 81b7c795e..5a85c2deb 100644 --- a/swift-sdk/IterableAPI.swift +++ b/swift-sdk/IterableAPI.swift @@ -688,6 +688,58 @@ import UIKit implementation?.inAppConsume(message: message, location: location, source: source) } + /// Subscribe user by email or userId + /// + /// - Parameters: + /// - email: Subscribe by email + /// - usrId: Subscribe by userId, but it's optional + /// - subscriptionId: Subscription Group ID + /// - subscriptionGroup: Subscription Group Name + /// - onSuccess: `OnSuccessHandler` to invoke if update is successful + /// - onFailure: `OnFailureHandler` to invoke if update fails + /// + /// - SeeAlso: OnSuccessHandler, OnFailureHandler + @objc(subscribeUser:userId:subscriptionId:subscriptionGroup:onSuccess:onFailure:) + public static func subscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) { + implementation?.subscribeUser(_email: _email, + userId: userId, + subscriptionId: subscriptionId, + subscriptionGroup: subscriptionGroup, + onSuccess: onSuccess, + onFailure: onFailure) + } + + /// UnSubscribe user by email or userId + /// + /// - Parameters: + /// - email: Subscribe by email + /// - usrId: Subscribe by userId, but it's optional + /// - subscriptionId: Subscription Group ID + /// - subscriptionGroup: Subscription Group Name + /// - onSuccess: `OnSuccessHandler` to invoke if update is successful + /// - onFailure: `OnFailureHandler` to invoke if update fails + /// + /// - SeeAlso: OnSuccessHandler, OnFailureHandler + @objc(unSubscribeUser:userId:subscriptionId:subscriptionGroup:onSuccess:onFailure:) + public static func unSubscribeUser(_email: String, + userId: String?, + subscriptionId: String, + subscriptionGroup: String, + onSuccess: OnSuccessHandler? = nil, + onFailure: OnFailureHandler? = nil) { + implementation?.unSubscribeUser(_email: _email, + userId: userId, + subscriptionId: subscriptionId, + subscriptionGroup: subscriptionGroup, + onSuccess: onSuccess, + onFailure: onFailure) + } + /// Tracks analytics data from a session of using an inbox UI /// NOTE: this is not normally used publicly, but is needed for our React Native SDK implementation ///