{"_id":"5b4dcdfc666e630003bca202","project":"55dd9f2e0efd5821000d54d9","version":{"_id":"55dd9f4dab0e4d210045aae9","__v":45,"project":"55dd9f2e0efd5821000d54d9","createdAt":"2015-08-26T11:13:17.024Z","releaseDate":"2015-08-26T11:13:17.024Z","categories":["55dd9f4dab0e4d210045aaea","55ddb5fa9067202b00ddff6f","55e0472c6bad670d0081f213","55e04764a44fae0d00214671","55e047a9a44fae0d00214672","55e047b258c5460d0076a9a7","55e95e337fc27b2d00d32cf2","55e979bda7ca823900ad549a","55edb8c18dcb210d0056900b","55f0365c8563861700a33765","55f03677d58f9b1900acf996","55f036938eeefc23001ea5de","55f036a38563861700a33767","55f036c08563861700a33769","55f036d02911b72100482cd7","55f036e92911b72100482cd9","55f036fa8563861700a3376b","55f0370ee507711900e58c69","55f0371df6101b1900c70700","55f0374f2911b72100482cdb","55f0375e2911b72100482cdc","560eb0f659cb8d0d0015cd52","560eb25239fad419002ae1e0","561fb64d4d67490d00804b2a","562b9f775a39cd0d009aff22","562ba0505a39cd0d009aff23","562ba149d56bc30d00f0cb18","562ba595f68a5f0d007b1f3b","562ba78fd56bc30d00f0cb1b","562ba8b95a39cd0d009aff27","562baadf6562140d001501d2","562bab37f68a5f0d007b1f3d","562bc1bf9ebc950d000f7523","562bc99ced4bea0d00c11dfa","562bd29c1b98640d00714520","562bd5875a39cd0d009aff60","562bdfabff2da50d002c0aaf","562be0bd5a39cd0d009aff75","57a0b476d8313e1900454439","5b19051beece890003020163","5b34ded01cb20f000391ad6d","5b3a325acffe770003fd29e5","5b3c737a7f7b890003365501","5b3c929b367036000391b11e","5b7c1e210dc2e20003871521"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"2.0.0","version":"2"},"category":{"_id":"5b34ded01cb20f000391ad6d","project":"55dd9f2e0efd5821000d54d9","version":"55dd9f4dab0e4d210045aae9","__v":0,"sync":{"url":"","isSync":false},"reference":false,"createdAt":"2018-06-28T13:12:48.898Z","from_sync":false,"order":1,"slug":"push-notifications","title":"Push notifications"},"user":"5a251846c297dc0012e531cd","githubsync":"","__v":0,"parentDoc":null,"updates":[],"next":{"pages":[],"description":""},"createdAt":"2018-07-17T11:07:40.065Z","link_external":false,"link_url":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":4,"body":"iOS SDK allows your app users to receive push notifications from your Engagement Cloud account. Engagement Cloud collects your user's email addresses which is then used to identify them so you can send push notifications.\n[block:html]\n{\n  \"html\": \"<div id=\\\"userMap\\\">\\n<div class=\\\"content\\\"><a href=\\\"https://developer.dotdigital.com/v2/docs/creating-a-push-notification-profile\\\"><div class=\\\"box box1\\\">Create a push notification profile in Engagement Cloud</div></a></div>\\n<div class=\\\"arrow\\\">→</div>\\n<div class=\\\"content\\\"><a href=\\\"https://developer.dotdigital.com/v2/docs/creating-a-json-web-token\\\"><div class=\\\"box box2\\\">Create a class or function that generates a JSON Web Token</div></a></div>\\n<div class=\\\"arrow\\\">→</div>\\n<div class=\\\"content\\\"><a href=\\\"https://developer.dotdigital.com/v2/docs/setting-up-push-notifications#section-mobile-sdk-options\\\"><div class=\\\"box box3 active\\\">Use one of our mobile SDKs in your app</div></a></div>\\n<div class=\\\"clearfix\\\"></div></div>\\n\\n<style>\\n  .box {\\n    padding: 10px;\\n    border: 2px solid #000;\\n    width: 120px;\\n    height: 120px;\\n    background-color: #EAEAEA;\\n    hyphens: auto;\\n    float: left;\\n    font-size: 12px;\\n}\\n\\n.box:hover {\\n    background-color: #82bc42;\\n}\\n\\n.box.active {\\n    background-color: #82bc42;\\n}\\n\\n#userMap {\\n    overflow-x: auto;\\n    overflow-y: auto;\\n    padding: 20px;\\n    min-width: 770px;\\n}\\n\\n#userMap a:hover {\\n    text-decoration: none;\\n  }\\n\\ndiv.arrow {\\n    max-width: 50px;\\n    margin-left: 15px;\\n    margin-right: 15px;\\n    font-size: 50px;\\n}\\n\\n\\n#userMap div.arrow, #userMap div.content {\\n    float: left;\\n}\\n\\n.clearfix {\\n    clear: both;\\n}\\n\\n\\n#userMap div.arrow {\\n    position: relative;\\n    top: 45px;\\n}\\n\\n.box1 {\\n    margin-left:0px;\\n}\\n\\ndiv.box.box1 {\\n    margin-left: -20px;\\n}\\n\\n</style>\"\n}\n[/block]\nOur iOS SDK uses the [Apple Push Notification Service (APNs)](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1) to send push notifications to your contacts. \n\nTo set up push notifications for native iOS apps, complete the following tasks:\n\n1. [Install the iOS SDK](#section-installing-the-ios-sdk)\n2. [Configure the iOS SDK](#section-configuring-the-ios-sdk)\n3. [Initialise the iOS SDK](#section-initialising-the-ios-sdk)\n4. [Add an email address the the iOS app user's profile](#section-adding-an-email-address-to-the-ios-app-user-s-profile)\n\n# Installing the iOS SDK\n\nIn order to install the SDK, we will use CMPComapiFoundation, a dependency manager for iOS/MacOS projects. To add the iOS SDK to your Xcode project with CMPComapiFoundation, do the following: \n\n1. Add the iOS SDK\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"# other podfile info\\n\\ntarget '*Your-Target*'\\nuse_frameworks!\\n\\npod 'CMPComapiFoundation'\\n\\nend\",\n      \"language\": \"objectivec\",\n      \"name\": \"Objective-C\"\n    },\n    {\n      \"code\": \"use_frameworks!\\n\\npod 'ComapiFoundation'\",\n      \"language\": \"swift\",\n      \"name\": null\n    }\n  ]\n}\n[/block]\n2. Install the iOS SDK\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"$ pod install\",\n      \"language\": \"shell\"\n    }\n  ]\n}\n[/block]\n3. Import the iOS SDK in your Objective-C or Swift file\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"#import <CMPComapiFoundation/CMPComapiFoundation.h>\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"import ComapiFoundation\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Initialise\nTo initialise the Comapi SDK, you will need a few pre-requisites listed below:\n\n  * A configured API Space\n  * An authentication provider that can generate a JWT (JSON Web Token) that matches the auth scheme configured for your Api Space\n  * The generated JWT must include the provided nonce as a claim in the generated JWT\n\nIn order for the client to be able to start a session, the config's authenticationDelegate object must conform to the protocol's method:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"NSString *id = <Portal's authentication tab ID claim value>;\\nNSString *issuer = <Portal's authentication tab issuer value>;\\nNSString *audience = <Portal's authentication tab audience value>;\\nNSString *secret = <Portal's authentication tab secret value>;\\n  \\n- (void)client:(CMPComapiClient *)client didReceiveAuthenticationChallenge:(CMPAuthenticationChallenge *)challenge completion:(void (^)(NSString * _Nullable))continueWithToken {\\n  \\t// request a JWT token from your provider (backend server)\\n  \\t// example call\\n  \\t[YourProviderServer getTokenForNonce:challenge.nonce id:id issuer:issuer audience:audience secret:secret completion:^(NSString * token, NSError * error) {\\n    \\t\\t// call continueWithToken block with generated token\\n        if (token && !error) {\\n            continueWithToken(token);\\n        }\\n    }];\\n}\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let id: String = <Portal's authentication tab ID claim value>\\nlet issuer: String = <Portal's authentication tab issuer value>\\nlet audience: String = <Portal's authentication tab audience value>\\nlet secret: String = <Portal's authentication tab secret value>\\n\\nfunc client(_ client: ComapiClient, didReceive challenge: \\t  \\t\\t\\tAuthenticationChallenge, completion continueWithToken: :::at:::escaping (String?) -> Void) {\\n\\t\\t// request a JWT token from your provider (backend server)\\n  \\t// example call\\n    YourProviderServer.getToken(nonce: challenge.nonce, id: id, issuer: issuer,  \\t\\t audience: audience, completion: { token: String?, error: Error? in\\n    \\t\\t// call continueWithToken block with generated token\\n        if (token != nil && error == nil) {\\n            continueWithToken(token!);\\n        }\\n    })\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nA JWT is used to securely transmit information between parties as a JSON object; it's digitally signed, therefore the information can be verified and trusted.  For more information on JWT, click [here](https://jwt.io/introduction/).\n\nThe JWT needs to include claims from the authentication panel in the dashboard. For further guidance, please click [here](https://developer.dotdigital.com/v2/docs/creating-a-push-notification-profile#in-engagement-cloud).\n\nHere's an example implementation of a token generator in Objective-C and Swift using JWT:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"#import \\\"CMPAuthenticationManager.h\\\"\\n#import <JWT/JWT.h>\\n\\n@implementation CMPAuthenticationManager\\n\\n+ (NSString *)generateTokenForNonce:(NSString *)nonce profileID:(NSString *)profileID issuer:(NSString *)issuer audience:(NSString *)audience secret:(NSString *)secret {\\n    NSDate *now = [NSDate date];\\n    NSDate *exp = [NSCalendar.currentCalendar dateByAddingUnit:NSCalendarUnitDay value:30 toDate:now options:0];\\n    \\n    NSDictionary *headers = @{@\\\"typ\\\" : @\\\"JWT\\\"};\\n    NSDictionary *payload = @{@\\\"nonce\\\" : nonce,\\n                               @\\\"sub\\\" : profileID,\\n                               @\\\"iss\\\" : issuer,\\n                               @\\\"aud\\\" : audience,\\n                               @\\\"iat\\\" : [NSNumber numberWithDouble:now.timeIntervalSince1970],\\n                               @\\\"exp\\\" : [NSNumber numberWithDouble:exp.timeIntervalSince1970]};\\n    \\n    NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];\\n    id<JWTAlgorithm> algorithm = [JWTAlgorithmFactory algorithmByName:@\\\"HS256\\\"];\\n    \\n    NSString *token = [JWTBuilder encodePayload:payload].headers(headers).secretData(secretData).algorithm(algorithm).encode;\\n    return token;\\n}\\n\\n@end\\n  \\n/* Note that this should preferably be generated by your backend, the app should only retreive the token through an HTTP call */\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"import JWT\\n\\nclass JWTokenGenerator {\\n    \\n    struct AuthHeaders {\\n        static let HeaderType = \\\"JWT\\\"\\n    }\\n    \\n    static func generate(tokenFor nonce: String, profileId: String, issuer: String, audience: String, secret: String) -> String {\\n        let now = Date()\\n        let exp = Calendar.current.date(byAdding: .day, value: 30, to: now)!\\n        \\n        let base64SecretKey = secret.data(using: .utf8)!\\n        \\n        let headers = [\\\"typ\\\" : NSString.init(string: AuthHeaders.HeaderType)] as [AnyHashable : Any]\\n        \\n        let claims = [\\\"nonce\\\" : NSString.init(string: nonce),\\n                      \\\"sub\\\" : NSString.init(string: profileId),\\n                      \\\"iss\\\" : NSString.init(string: issuer),\\n                      \\\"aud\\\" : NSString.init(string: audience),\\n                      \\\"iat\\\" : NSNumber(value: now.timeIntervalSince1970),\\n                      \\\"exp\\\" : NSNumber(value: exp.timeIntervalSince1970)] as [AnyHashable : Any]\\n        \\n        let algorithm = JWTAlgorithmFactory.algorithm(byName: \\\"HS256\\\")\\n        \\n        let e = JWTBuilder.encodePayload(claims)!\\n        \\n        let h = e.headers(headers)!\\n        let s = h.secretData(base64SecretKey)!\\n        let b = s.algorithm(algorithm)!\\n        let token = b.encode\\n\\n        return token!\\n    }\\n}\\n\\n/* Note that this should preferably be generated by your backend, the app should only retreive the token through an HTTP call */\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Configuring the iOS SDK\n\nBefore you can configure the iOS SDK, you need the following:\n\n* The value of the [API space ID field in Engagement Cloud](doc:creating-a-push-notification-profile#section-finding-your-api-space-id)\n* A class that creates a JWT token\n* Your app must be set up so that it [asks the user for permission to send push notifications](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications).\n\nTo configure the iOS SDK:\n\n1. Create a new instance of the `ComapiConfig` class and store it in a variable\n\n2. As the first argument of the `ComapiConfig` instance, pass your API Space ID\n\n3. As the second argument of the `ComapiConfig` instance, pass an instance of [a class that creates a JWT token](#section-ios-jwt-code-sample)\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"// create a config object with your api-space-id and an object conforming to CMPAuthenticationDelegate protocol;\\nCMPComapiConfig *config = [[CMPComapiConfig alloc] initWithApiSpaceID:@\\\"<API_SPACE_ID>\\\" authenticationDelegat:<CMPAuthenticationDelegate_Conforming_Object>];\\n\\nCMPComapiClient *client = [CMPComapi initialiseWithConfig:config];\\n// we can use the client object now\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let config = ComapiConfig(apiSpaceId: \\\"<API_SPACE_ID>\\\", authenticationDelegate: ChallengeHandler())\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Retrieving the client\n\nThe client can be retrieved either as a separate object using:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"CMPComapiClient *client = [CMPComapi initialiseWithConfig:config];\\n// client instance ready to use\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"guard let client = Comapi.initialise(with: config) else {\\n\\t\\t// initialisation error\\n    return\\n}\\n// client instance ready to use\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nor as a singleton:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[CMPComapi initialiseSharedInstanceWithConfig:config];\\n\\nCMPComapiClient *client = [Comapi shared];\\n// shared client ready to use\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"Comapi.initialiseSharedInstance(with: config)\\n\\nguard let client = Comapi.shared else {\\n\\t\\t// initialisation error\\n    return\\n}\\n// shared client ready to use\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Start session\n\nCalling Comapi services requires an active session. Once you successfully initialise and retrieve a client object, call:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.session startSessionWithCompletion:^{\\n  // session successfully created\\n} failure:^(NSError * _Nullable error) {\\n  // error ocurred\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.session.startSession(completion: { in\\n    // session successfully created\\n}) { (error) in\\n    // error\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nA session will also be automatically created once you call and of the API services, so the above step is not required.\n\nTo end the current session, call:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.session endSessionWithCompletion:^(CMPResult<NSNumber *> * result) {\\n    if (result.error) {\\n      // error occurred\\n    } else {\\n      BOOL success = [result.object boolValue];\\n      if (success) {\\n        // session successfully ended\\n      }     \\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.session.endSession(completion: { result in\\n    if let err = result.error {\\n        // error occurred\\n    } else if let obj = result.object?.boolValue, obj == true {\\n        // success\\n    }\\n})\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Configuring your app to ask users' permission to send them push notifications\n\nTo receive push notifications, app users must give their permission.\n\nCall the `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)` [method](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application?language=swift) to get the `deviceToken` string and pass it to the `setPushToken()` method on the `ComapiClient` object. \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {\\n    NSMutableString *token = [NSMutableString string];\\n    \\n    const char *data = [deviceToken bytes];\\n    for (NSUInteger i = 0; i < [deviceToken length]; i++) {\\n        [token appendFormat:@\\\"%.2hhx\\\", data[i]];\\n    }\\n  \\n    if (token) {\\n        [client setPushToken:token completion:^(CMPResult<NSNumber *> * result) {\\n          \\tBOOL success = [result.object boolValue];\\n            if (result.error || !success) {\\n                // error occurred\\n            } else {\\n                // configuration successful\\n            }\\n        }];\\n    }\\n    \\n    // rest of you push notification code\\n}\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"func application(_ application: UIApplication,\\n                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\\nlet token = deviceToken.map { String(format: \\\"%02.2hhx\\\", $0) }.joined()\\n//Comapi.shared gets the ComapiClient object\\nComapi.shared.setPushToken(token, completion: { _ in }\\n            \\n/* Put your custom code here */\\n\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# Displaying push notifications when the app is in the foreground\n\nPush notifications are displayed only while the app is in the background. These notification are sent to the notification center and launch your app when users tap them.\n\nIf you want to display the push notification message while the app is in the foreground, do one of the following, depending on the iOS version that your app is running on:\n\n**For iOS 9** \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {\\n    if (application.applicationState == UIApplicationStateActive) {\\n        \\n    }\\n}\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {\\n  if application.applicationState == .active {\\n    // Handle the notification here\\n  }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n**For iOS 10 and above** \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {\\n    \\n    completionHandler();\\n}\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"func userNotificationCenter(_ center: UNUserNotificationCenter,\\n                                willPresent notification: UNNotification,\\n                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {\\n  completionHandler(.alert)\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n# References\n\n## Adding an email address to the iOS app user's profile\n\nPush notifications can be sent from Engagement Cloud only to users who have a profile that contains an email address.\n\nTo create a profile for the user, a session must be started. A session is started automatically after initialisation, but you can also start it explicitly by calling the `client.services.session.startSession()` method.\n\nWhen a session is started, the JWT token is used to create the user's profile ID ('profileId' string). This profile ID remains the same until a session is stopped.\n\nWhen a session is stopped and started again, a new JWT token is created and used to create a new profile ID.\n\nA session is stopped in any of the following circumstances:\n\n* The user uninstalls the app, and then reinstalls it\n* The user clears all of the app's data\n\nYou can also stop a session by calling the `client.services.session.endSession()` method.\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Creating and updating users with push tokens\",\n  \"body\": \"When you add an email address to a user's profile via the iOS SDK, a contact will automatically be created and updated with push tokens in Engagement Cloud.\"\n}\n[/block]\n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.profile patchProfileForProfileID:@\\\"<PROFILE-ID>\\\" attributes:@{@\\\"email\\\" : @\\\"new@email.com\\\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.profile.updateCurrentProfile([\\\"email\\\": \\\"exampleappuser1@gmail.com\\\"]) { result in\\n  // completion handler\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Ask users to enter their email address\",\n  \"body\": \"You should ask users if they'd like to receive push notifications from you and prompt them to enter their email address.\"\n}\n[/block]\nNow, when a user launches your app, the user's profile data is sent to Engagement Cloud and (if that profile contains an email address that belongs to one of your contacts) that contact can now receive push notifications from Engagement Cloud. :tada:\n[block:callout]\n{\n  \"type\": \"success\",\n  \"body\": \"[Send a push notification to your app](https://support.dotdigital.com/hc/en-gb/articles/360001827870) from the program builder in Engagement Cloud.\",\n  \"title\": \"Next steps\"\n}\n[/block]\n## Configuring logs\n\nYou can set internal file logs and console logs to the following levels:\n\n* `verbose`\n* `debug`\n* `info`\n* `warning`\n* `error`\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"Log.consoleDestination.minimumLevel = .error\\nLog.fileDestination.minimumLevel = .info\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n## Websocket\n\nWhenever ComapiClient is configured and authenticated, a new session is started which opens a WebSocket connection. This allows for a two-way real-time communication between the client and the server. For more information on WebSocket visit the official RFC site. When open, the socket provides live updates for the profile and conversations which you are a participant of. You can subscribe for these updates to update your views and models.\n\n## Add event listener\n\nRegister the delegate for the incoming events with the CMPComapiClient object. The delegate should conform to the CMPEventListener protocol.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client addEventDelegate:self];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.add(self)\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nTo receive the event, implement the following method from CMPEventListener protocol:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"- (void)client:(CMPComapiClient *)client didReceiveEvent:(CMPEvent *)event {\\n  \\tswitch (event.type) {\\n        case CMPEventTypeConversationMessageSent:\\n      \\t\\t\\t// message sent event received\\n            break;\\n        case CMPEventTypeConversationParticipantTypingOff:\\n      \\t\\t\\t// participant typing off event received\\n            break;\\n        case CMPEventTypeConversationParticipantTyping:\\n      \\t\\t\\t// participant typing event received\\n            break;\\n        case CMPEventTypeConversationMessageRead:\\n      \\t\\t\\t// message read event received\\n            break;\\n      \\t// case .other events:\\n        default:\\n      \\t\\t\\t// unknown event type\\n            break;\\n    }\\n}\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"func client(_ client: ComapiClient, didReceive event: Event) {\\n\\t\\tswitch event.type {\\n        case .conversationMessageSent:\\n            // message sent event received\\n        case .conversationParticipantTypingOff:\\n            // participantTypingOff event received\\n        case .conversationParticipantTyping:\\n            // participantTyping event received\\n        case .conversationMessageRead:\\n        \\t\\t// message read event received\\n        // case .other events:\\n        \\n        default:\\n            // unknown event type\\n\\t\\t}\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nPlease note that you can register multiple listeners in your code to handle events in different parts of your application. The events are broadcasted to all registered delegates.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client removeEventDelegate:self];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.remove(self)\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nRegister the delegate for the incoming events with the CMPComapiClient object. The delegate should conform to the CMPEventListener protocol.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client removeEventDelegate:self];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.remove(self)\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n## Available events\n[block:parameters]\n{\n  \"data\": {\n    \"h-0\": \"Event type\",\n    \"h-1\": \"Event subtype\",\n    \"0-0\": \"profile\",\n    \"0-1\": \"update\",\n    \"0-2\": \"Sent when a user's profile is updated.\",\n    \"1-0\": \"conversation\",\n    \"1-1\": \"participantAdded\",\n    \"1-2\": \"Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.\",\n    \"2-0\": \"conversation\",\n    \"2-1\": \"participantUpdated\",\n    \"2-2\": \"Sent when a participant role is updated in a conversation.\",\n    \"3-0\": \"conversation\",\n    \"3-1\": \"participantRemoved\",\n    \"3-2\": \"Sent when a participant is removed from a conversation.\",\n    \"4-0\": \"conversation\",\n    \"4-1\": \"delete\",\n    \"4-2\": \"Sent when a conversation is deleted.\",\n    \"5-0\": \"conversation\",\n    \"5-1\": \"update\",\n    \"5-2\": \"Sent when a conversation's details were updated.\",\n    \"6-0\": \"conversation\",\n    \"6-1\": \"undelete\",\n    \"6-2\": \"Sent when a conversation is restored.\",\n    \"7-0\": \"conversation\",\n    \"7-1\": \"create\",\n    \"7-2\": \"Sent when a new conversation was created.\",\n    \"8-0\": \"conversation\",\n    \"8-1\": \"participantTyping\",\n    \"8-2\": \"Sent when one of the participants is typing a new message.\",\n    \"9-0\": \"conversation\",\n    \"9-1\": \"participantTypingOff\",\n    \"9-2\": \"Sent when the other participant stops typing.\",\n    \"10-0\": \"conversationMessage\",\n    \"10-1\": \"delivered\",\n    \"10-2\": \"Sent when on of the participants updated the message status to 'delivered'.\",\n    \"11-0\": \"conversationMessage\",\n    \"11-1\": \"read\",\n    \"11-2\": \"Sent when one of the participants updated the message status to 'read'.\",\n    \"12-0\": \"conversationMessage\",\n    \"12-1\": \"sent\",\n    \"12-2\": \"Sent when a new message appeared in a conversation. This event will also be delivered to the message sender.\"\n  },\n  \"cols\": 3,\n  \"rows\": 13\n}\n[/block]\nFor all events you can access id, apiSpace and name and context properties. For other events, there are other properties you can access, as follows:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"profile.profileId\",\n      \"language\": \"text\",\n      \"name\": \"profile\"\n    }\n  ]\n}\n[/block]\n##Conversation\nBelow are listed properties specific for the conversation event subtypes.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"conversation.conversationId\\nconversation.payload.profileId\\nconversation.payload.role\",\n      \"language\": \"text\",\n      \"name\": \"participantAdded\"\n    },\n    {\n      \"code\": \"conversation.conversationId\\nconversation.payload.profileId\\nconversation.payload.role\",\n      \"language\": \"text\",\n      \"name\": \"participantUpdated\"\n    },\n    {\n      \"code\": \"conversation.conversationId\\nconversation.payload.profileId\\nconversation.payload.role\",\n      \"language\": \"text\",\n      \"name\": \"participantRemoved\"\n    },\n    {\n      \"code\": \"conversation.conversationId\\nconversation.payload.date\",\n      \"language\": \"text\",\n      \"name\": \"delete\"\n    },\n    {\n      \"code\": \"conversation.profileId\\nconversation.payload.roles\\nconversation.payload.isPublic\\nconversation.payload.participants\",\n      \"language\": \"text\",\n      \"name\": \"undelete\"\n    },\n    {\n      \"code\": \"conversation.conversationId\\nconversation.payload.description\\nconversation.payload.roles\",\n      \"language\": \"text\",\n      \"name\": \"update\"\n    },\n    {\n      \"code\": \"conversation.profileId\\nconversation.payload.roles\\nconversation.payload.isPublic\\nconversation.payload.participants\",\n      \"language\": \"text\",\n      \"name\": \"create\"\n    },\n    {\n      \"code\": \"conversation.account\\nconversation.payload.profileId\\ncovenrsation.payload.conversationId\",\n      \"language\": \"text\",\n      \"name\": \"participantTyping\"\n    },\n    {\n      \"code\": \"conversation.account\\nconversation.payload.profileId\\ncovenrsation.payload.conversationId\",\n      \"language\": \"text\",\n      \"name\": \"participantTypingOff\"\n    }\n  ]\n}\n[/block]\nConversationMessage\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"conversationMessage.messageId\\nconversationMessage.conversationId\\nconversationMessage.profileId\\nconversationMessage.timestamp\",\n      \"language\": \"text\",\n      \"name\": \"delivered\"\n    },\n    {\n      \"code\": \"conversationMessage.messageId\\nconversationMessage.conversationId\\nconversationMessage.profileId\\nconversationMessage.timestamp\",\n      \"language\": \"text\",\n      \"name\": \"sent\"\n    },\n    {\n      \"code\": \"conversationMessage.messageId\\nconversationMessage.metadata\\nconversationMessage.parts\\nconversationMessage.alert\",\n      \"language\": \"text\",\n      \"name\": \"read\"\n    }\n  ]\n}\n[/block]\n## Client APIs\nYou can use **CMPComapi** client instance obtained in Initialisation to access SDK APIs.\n\n##Session\nGet the current **profileID**:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let profileId = client.profileID\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nCheck if the session was successfully created:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let isSessionSuccesfullyCreated = client.isSessionSuccessfullyCreated\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Services\nAccessing services is done by calling methods from the proper group:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"client.services.profile\\nclient.services.session\\nclient.services.messaging\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.profile\\nclient.services.session\\nclient.services.messaging\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nMost services use CMPResult object as a return value in completion blocks:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"@interface CMPResult<__covariant T> : NSObject\\n\\n@property (nonatomic, nullable) T object;\\n@property (nonatomic, nullable) NSError *error;\\n@property (nonatomic, nullable) NSString *eTag;\\n@property (nonatomic) NSInteger code;\\n\\n- (instancetype)init NS_UNAVAILABLE;\\n\\n- (instancetype)initWithObject:(nullable T)object error:(nullable NSError *)error eTag:(nullable NSString *)eTag code:(NSInteger)code;\\n\\n@end\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"open class Result<T> : NSObject where T : AnyObject {\\n\\n    open var object: T?\\n    open var error: Error?\\n    open var eTag: String?\\n    open var code: Int\\n\\n    public init(object: T?, error: Error?, eTag: String?, code: Int)\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nYou can get the ETage value from it, as well as an HTTP status code and potential error.\n\n##Profile services\n\nGet profile:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.profile getProfileForProfileID:@\\\"<PROFILE-ID>\\\" completion:^(CMPResult<CMPProfile *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\",\n      \"name\": null\n    },\n    {\n      \"code\": \"client.services.profile.getProfile(profileID: \\\"\\\") { (result) in\\n    if let err = result.error {\\n        // error occurred\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nQuery profiles with query builder:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.profile queryProfilesWithQueryElements:@[] completion:^(CMPResult<NSArray<CMPProfile *> *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.profile.queryProfiles(queryElements: [.init(key: \\\"email\\\", element: .contains, value: \\\"new\\\")]) { (result) in\\n    if let err = result.error {\\n        // error occurred\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nPatch profile:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.profile patchProfileForProfileID:@\\\"<PROFILE-ID>\\\" attributes:@{@\\\"email\\\" : @\\\"new@email.com\\\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.profile.patchProfile(profileID: \\\"profileId\\\", attributes: [\\\"email\\\" : \\\"new@email.com\\\"], eTag: nil) { (result) in\\n    if let err = result.error {\\n        // error occurred\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nUpdate profile:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.profile updateProfileForProfileID:@\\\"<PROFILE-ID>\\\" attributes:@{@\\\"email\\\" : @\\\"new@email.com\\\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.profile.updateProfile(profileID: \\\"profileId\\\", attributes: [\\\"email\\\" : \\\"new@email.com\\\"], eTag: nil) { (result) in\\n    if let err = result.error {\\n        // error occurred\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Messaging services\n\nCreate conversation:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"CMPRoleAttributes *ownerAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:YES canRemoveParticipants:YES];\\n\\n    CMPRoleAttributes *participantAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:NO canRemoveParticipants:NO];\\n\\n    CMPRoles *roles = [[CMPRoles alloc] initWithOwnerAttributes:ownerAttributes participantAttributes:participantAttributes];\\n\\nCMPConversationParticipant *owner = [[CMPConversationParticipant alloc] initWithID:profileID role:@\\\"owner\\\"];\\n\\nCMPNewConversation *newConversation = [[CMPNewConversation alloc] initWithID:@\\\"{id}\\\" name:@\\\"{name}\\\" description:@\\\"{description}\\\" roles:roles participants:@[owner] isPublic:@(NO)];\\n\\n[client.services.messaging addConversationWithConversation:weakSelf.conversation completion:^(CMPResult<CMPConversation *> *result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let participant = ConversationParticipant(id: \\\"{participantId}\\\", role: .participant)\\nlet ownerAttributes = RoleAttributes(canSend: true, canAddParticipants: true, canRemoveParticipants: true)\\nlet participantAttributes = RoleAttributes(canSend: true, canAddParticipants: false, canRemoveParticipants: false)\\nlet roles = Roles(ownerAttributes: ownerAttributes, participantAttributes: participantAttributes)\\nlet newConversation = NewConversation(id: \\\"{id}\\\", name: \\\"{name}\\\", description: \\\"{description}\\\", roles: roles, participants: [participant], isPublic: NSNumber.init(booleanLiteral: false))\\n\\nclient.services.messaging.addConversation(conversation: newConversation) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Query \n\nQuery all conversations:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[_client.services.messaging getConversationsWithProfileID:_client.profileID isPublic:YES completion:^(CMPResult<NSArray<CMPConversation *> *> * _Nonnull) {\\n    if (result.error) {\\n      \\t// error occurred\\n    } else {\\n      \\t// success\\n    }              \\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.getConversations(profileID: client.profileID!, isPublic: true) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.compactMap({ $0 as? Profile }) {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nQuery specific conversation:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging getConversationWithConversationID:@\\\"{id}\\\" completion:^(CMPResult<CMPConversation *> *result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.getConversation(conversationID: \\\"{id}\\\") { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nUpdate conversation:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"CMPConversationUpdate *update = [[CMPConversationUpdate alloc] initWithID:@\\\"{id}\\\" name:@\\\"{name}\\\" description:@\\\"{description}\\\" roles:roles isPublic:@(NO)];\\n\\n[client.services.messaging updateConversationWithConversationID:@\\\"{id}\\\" conversation:update eTag:nil completion:^(CMPResult<CMPConversation *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let ownerAttributes = RoleAttributes(canSend: true, canAddParticipants: true, canRemoveParticipants: true)\\nlet participantAttributes = RoleAttributes(canSend: true, canAddParticipants: false, canRemoveParticipants: false)\\nlet roles = Roles(ownerAttributes: ownerAttributes, participantAttributes: participantAttributes)\\nlet update = ConversationUpdate(id: \\\"{id}\\\", name: \\\"{name}\\\", description: \\\"{description}\\\", roles: roles, isPublic: NSNumber.init(booleanLiteral: false))\\n        \\nclient.services.messaging.updateConversation(conversationID: \\\"{id}\\\", conversation: update, eTag: nil) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nDelete conversation:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging deleteConversationWithConversationID:@\\\"{id}\\\" eTag:nil completion:^(CMPResult<NSNumber *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.deleteConversation(conversationID: \\\"{id}\\\", eTag: nil) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.boolValue, obj == true {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Participants\n\nGet participants:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging getParticipantsWithConversationID:@\\\"{id}\\\" completion:^(CMPResult<NSArray<CMPConversationParticipant *> *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.getParticipants(conversationID: \\\"{id}\\\") { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.compactMap({ $0 as ConversationParticipant }) {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nAdd participants:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"NSArray<CMPConversationParticipant *> *participants = @[[[CMPConversationParticipant alloc] initWithID:@\\\"@{id1}\\\" role:@\\\"{role}\\\"], [[CMPConversationParticipant alloc] initWithID:@\\\"@{id2}\\\" role:@\\\"{role}\\\"]];\\n\\n[client.services.messaging addParticipantsWithConversationID:@\\\"{id}\\\" participants:participants completion:^(CMPResult<NSNumber *> * result) {\\n    if (result.error) {\\n    \\t\\t// error occurred\\n    } else {\\n      \\t// success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let participant = ConversationParticipant(id: \\\"{participantId}\\\", role: .participant)\\n        \\nclient.services.messaging.addParticipants(conversationID: \\\"{id}\\\", participants: []) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.boolValue, obj == true {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nRemove participants:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging removeParticipantsWithConversationID:@\\\"{id}\\\" participants:participants completion:^(CMPResult<NSNumber *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let participant = ConversationParticipant(id: \\\"{participantId}\\\", role: .participant)\\n        \\nclient.services.messaging.removeParticipants(conversationID: \\\"{id}\\\", participants: [participant]) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.boolValue, obj == true {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Messages\n\nQuery messages:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging getMessagesWithConversationID:@\\\"{id}\\\" limit:100 from:0 completion:^(CMPResult<CMPGetMessagesResult *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.getMessages(conversationID: \\\"{id}\\\") { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nCreate message:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"CMPMessagePart *part = [[CMPMessagePart alloc] initWithName:@\\\"{name}\\\" type:@\\\"{type}\\\" url:nil data:@\\\"{data}\\\" size:@(123)];\\n\\nCMPMessageAlert *alert = [[CMPMessageAlert alloc] initWithPlatforms:[[CMPMessageAlertPlatforms alloc] initWithApns:@{} fcm:@{}]];\\n    \\nCMPSendableMessage *message = [[CMPSendableMessage alloc] initWithMetadata:@{} parts:@[part] alert:alert];\\n\\n[client.services.messaging sendMessage:message toConversationWithID:@\\\"{id}\\\" completion:^(CMPResult<CMPSendMessagesResult *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"let text = \\\"some message\\\"\\nlet part = MessagePart(name: \\\"part1\\\", type: \\\"plain/text\\\", url: nil, data: text, size: NSNumber(integerLiteral: text.count))\\nlet msg = SendableMessage(metadata: [\\\"custom field\\\" : \\\"custom value\\\"], parts: [part], alert: nil)\\n        \\nclient.services.messaging.send(message: msg, conversationID: \\\"{id}\\\") { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\nUpdate messages status:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging updateStatusForMessagesWithIDs:@[@\\\"{id1}\\\", @\\\"{id2}\\\"] status:@\\\"read\\\" conversationID:@\\\"{id}\\\" timestamp:[NSDate date] completion:^(CMPResult<NSNumber *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.updateStatus(messageIDs: [\\\"{id1}\\\", \\\"{id2}\\\"], status: .delivered, conversationID: \\\"{id}\\\", timestamp: Date()) { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.boolValue, obj == true {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]\n##Events\n\nQuery events:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"[client.services.messaging queryEventsWithConversationID:@\\\"{id}\\\" limit:0 from:100 completion:^(CMPResult<NSArray<CMPEvent *> *> * result) {\\n    if (result.error) {\\n        // error occurred\\n    } else {\\n        // success\\n    }\\n}];\",\n      \"language\": \"objectivec\"\n    },\n    {\n      \"code\": \"client.services.messaging.queryEvents(conversationID: \\\"{id}\\\") { (result) in\\n    if let err = result.error {\\n        // error\\n    } else if let obj = result.object?.compactMap({ $0 as? Event }) {\\n        // success\\n    }\\n}\",\n      \"language\": \"swift\"\n    }\n  ]\n}\n[/block]","excerpt":"A tutorial for adding the iOS SDK code to your app to allow your app users to receive push notifications from your Engagement Cloud account.","slug":"ios-sdk","type":"basic","title":"Using the iOS SDK"}

Using the iOS SDK

A tutorial for adding the iOS SDK code to your app to allow your app users to receive push notifications from your Engagement Cloud account.

iOS SDK allows your app users to receive push notifications from your Engagement Cloud account. Engagement Cloud collects your user's email addresses which is then used to identify them so you can send push notifications. [block:html] { "html": "<div id=\"userMap\">\n<div class=\"content\"><a href=\"https://developer.dotdigital.com/v2/docs/creating-a-push-notification-profile\"><div class=\"box box1\">Create a push notification profile in Engagement Cloud</div></a></div>\n<div class=\"arrow\">→</div>\n<div class=\"content\"><a href=\"https://developer.dotdigital.com/v2/docs/creating-a-json-web-token\"><div class=\"box box2\">Create a class or function that generates a JSON Web Token</div></a></div>\n<div class=\"arrow\">→</div>\n<div class=\"content\"><a href=\"https://developer.dotdigital.com/v2/docs/setting-up-push-notifications#section-mobile-sdk-options\"><div class=\"box box3 active\">Use one of our mobile SDKs in your app</div></a></div>\n<div class=\"clearfix\"></div></div>\n\n<style>\n .box {\n padding: 10px;\n border: 2px solid #000;\n width: 120px;\n height: 120px;\n background-color: #EAEAEA;\n hyphens: auto;\n float: left;\n font-size: 12px;\n}\n\n.box:hover {\n background-color: #82bc42;\n}\n\n.box.active {\n background-color: #82bc42;\n}\n\n#userMap {\n overflow-x: auto;\n overflow-y: auto;\n padding: 20px;\n min-width: 770px;\n}\n\n#userMap a:hover {\n text-decoration: none;\n }\n\ndiv.arrow {\n max-width: 50px;\n margin-left: 15px;\n margin-right: 15px;\n font-size: 50px;\n}\n\n\n#userMap div.arrow, #userMap div.content {\n float: left;\n}\n\n.clearfix {\n clear: both;\n}\n\n\n#userMap div.arrow {\n position: relative;\n top: 45px;\n}\n\n.box1 {\n margin-left:0px;\n}\n\ndiv.box.box1 {\n margin-left: -20px;\n}\n\n</style>" } [/block] Our iOS SDK uses the [Apple Push Notification Service (APNs)](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1) to send push notifications to your contacts. To set up push notifications for native iOS apps, complete the following tasks: 1. [Install the iOS SDK](#section-installing-the-ios-sdk) 2. [Configure the iOS SDK](#section-configuring-the-ios-sdk) 3. [Initialise the iOS SDK](#section-initialising-the-ios-sdk) 4. [Add an email address the the iOS app user's profile](#section-adding-an-email-address-to-the-ios-app-user-s-profile) # Installing the iOS SDK In order to install the SDK, we will use CMPComapiFoundation, a dependency manager for iOS/MacOS projects. To add the iOS SDK to your Xcode project with CMPComapiFoundation, do the following: 1. Add the iOS SDK [block:code] { "codes": [ { "code": "# other podfile info\n\ntarget '*Your-Target*'\nuse_frameworks!\n\npod 'CMPComapiFoundation'\n\nend", "language": "objectivec", "name": "Objective-C" }, { "code": "use_frameworks!\n\npod 'ComapiFoundation'", "language": "swift", "name": null } ] } [/block] 2. Install the iOS SDK [block:code] { "codes": [ { "code": "$ pod install", "language": "shell" } ] } [/block] 3. Import the iOS SDK in your Objective-C or Swift file [block:code] { "codes": [ { "code": "#import <CMPComapiFoundation/CMPComapiFoundation.h>", "language": "objectivec" }, { "code": "import ComapiFoundation", "language": "swift" } ] } [/block] # Initialise To initialise the Comapi SDK, you will need a few pre-requisites listed below: * A configured API Space * An authentication provider that can generate a JWT (JSON Web Token) that matches the auth scheme configured for your Api Space * The generated JWT must include the provided nonce as a claim in the generated JWT In order for the client to be able to start a session, the config's authenticationDelegate object must conform to the protocol's method: [block:code] { "codes": [ { "code": "NSString *id = <Portal's authentication tab ID claim value>;\nNSString *issuer = <Portal's authentication tab issuer value>;\nNSString *audience = <Portal's authentication tab audience value>;\nNSString *secret = <Portal's authentication tab secret value>;\n \n- (void)client:(CMPComapiClient *)client didReceiveAuthenticationChallenge:(CMPAuthenticationChallenge *)challenge completion:(void (^)(NSString * _Nullable))continueWithToken {\n \t// request a JWT token from your provider (backend server)\n \t// example call\n \t[YourProviderServer getTokenForNonce:challenge.nonce id:id issuer:issuer audience:audience secret:secret completion:^(NSString * token, NSError * error) {\n \t\t// call continueWithToken block with generated token\n if (token && !error) {\n continueWithToken(token);\n }\n }];\n}", "language": "objectivec" }, { "code": "let id: String = <Portal's authentication tab ID claim value>\nlet issuer: String = <Portal's authentication tab issuer value>\nlet audience: String = <Portal's authentication tab audience value>\nlet secret: String = <Portal's authentication tab secret value>\n\nfunc client(_ client: ComapiClient, didReceive challenge: \t \t\t\tAuthenticationChallenge, completion continueWithToken: @escaping (String?) -> Void) {\n\t\t// request a JWT token from your provider (backend server)\n \t// example call\n YourProviderServer.getToken(nonce: challenge.nonce, id: id, issuer: issuer, \t\t audience: audience, completion: { token: String?, error: Error? in\n \t\t// call continueWithToken block with generated token\n if (token != nil && error == nil) {\n continueWithToken(token!);\n }\n })\n}", "language": "swift" } ] } [/block] A JWT is used to securely transmit information between parties as a JSON object; it's digitally signed, therefore the information can be verified and trusted. For more information on JWT, click [here](https://jwt.io/introduction/). The JWT needs to include claims from the authentication panel in the dashboard. For further guidance, please click [here](https://developer.dotdigital.com/v2/docs/creating-a-push-notification-profile#in-engagement-cloud). Here's an example implementation of a token generator in Objective-C and Swift using JWT: [block:code] { "codes": [ { "code": "#import \"CMPAuthenticationManager.h\"\n#import <JWT/JWT.h>\n\n@implementation CMPAuthenticationManager\n\n+ (NSString *)generateTokenForNonce:(NSString *)nonce profileID:(NSString *)profileID issuer:(NSString *)issuer audience:(NSString *)audience secret:(NSString *)secret {\n NSDate *now = [NSDate date];\n NSDate *exp = [NSCalendar.currentCalendar dateByAddingUnit:NSCalendarUnitDay value:30 toDate:now options:0];\n \n NSDictionary *headers = @{@\"typ\" : @\"JWT\"};\n NSDictionary *payload = @{@\"nonce\" : nonce,\n @\"sub\" : profileID,\n @\"iss\" : issuer,\n @\"aud\" : audience,\n @\"iat\" : [NSNumber numberWithDouble:now.timeIntervalSince1970],\n @\"exp\" : [NSNumber numberWithDouble:exp.timeIntervalSince1970]};\n \n NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];\n id<JWTAlgorithm> algorithm = [JWTAlgorithmFactory algorithmByName:@\"HS256\"];\n \n NSString *token = [JWTBuilder encodePayload:payload].headers(headers).secretData(secretData).algorithm(algorithm).encode;\n return token;\n}\n\n@end\n \n/* Note that this should preferably be generated by your backend, the app should only retreive the token through an HTTP call */", "language": "objectivec" }, { "code": "import JWT\n\nclass JWTokenGenerator {\n \n struct AuthHeaders {\n static let HeaderType = \"JWT\"\n }\n \n static func generate(tokenFor nonce: String, profileId: String, issuer: String, audience: String, secret: String) -> String {\n let now = Date()\n let exp = Calendar.current.date(byAdding: .day, value: 30, to: now)!\n \n let base64SecretKey = secret.data(using: .utf8)!\n \n let headers = [\"typ\" : NSString.init(string: AuthHeaders.HeaderType)] as [AnyHashable : Any]\n \n let claims = [\"nonce\" : NSString.init(string: nonce),\n \"sub\" : NSString.init(string: profileId),\n \"iss\" : NSString.init(string: issuer),\n \"aud\" : NSString.init(string: audience),\n \"iat\" : NSNumber(value: now.timeIntervalSince1970),\n \"exp\" : NSNumber(value: exp.timeIntervalSince1970)] as [AnyHashable : Any]\n \n let algorithm = JWTAlgorithmFactory.algorithm(byName: \"HS256\")\n \n let e = JWTBuilder.encodePayload(claims)!\n \n let h = e.headers(headers)!\n let s = h.secretData(base64SecretKey)!\n let b = s.algorithm(algorithm)!\n let token = b.encode\n\n return token!\n }\n}\n\n/* Note that this should preferably be generated by your backend, the app should only retreive the token through an HTTP call */", "language": "swift" } ] } [/block] # Configuring the iOS SDK Before you can configure the iOS SDK, you need the following: * The value of the [API space ID field in Engagement Cloud](doc:creating-a-push-notification-profile#section-finding-your-api-space-id) * A class that creates a JWT token * Your app must be set up so that it [asks the user for permission to send push notifications](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications). To configure the iOS SDK: 1. Create a new instance of the `ComapiConfig` class and store it in a variable 2. As the first argument of the `ComapiConfig` instance, pass your API Space ID 3. As the second argument of the `ComapiConfig` instance, pass an instance of [a class that creates a JWT token](#section-ios-jwt-code-sample) [block:code] { "codes": [ { "code": "// create a config object with your api-space-id and an object conforming to CMPAuthenticationDelegate protocol;\nCMPComapiConfig *config = [[CMPComapiConfig alloc] initWithApiSpaceID:@\"<API_SPACE_ID>\" authenticationDelegat:<CMPAuthenticationDelegate_Conforming_Object>];\n\nCMPComapiClient *client = [CMPComapi initialiseWithConfig:config];\n// we can use the client object now", "language": "objectivec" }, { "code": "let config = ComapiConfig(apiSpaceId: \"<API_SPACE_ID>\", authenticationDelegate: ChallengeHandler())", "language": "swift" } ] } [/block] # Retrieving the client The client can be retrieved either as a separate object using: [block:code] { "codes": [ { "code": "CMPComapiClient *client = [CMPComapi initialiseWithConfig:config];\n// client instance ready to use", "language": "objectivec" }, { "code": "guard let client = Comapi.initialise(with: config) else {\n\t\t// initialisation error\n return\n}\n// client instance ready to use", "language": "swift" } ] } [/block] or as a singleton: [block:code] { "codes": [ { "code": "[CMPComapi initialiseSharedInstanceWithConfig:config];\n\nCMPComapiClient *client = [Comapi shared];\n// shared client ready to use", "language": "objectivec" }, { "code": "Comapi.initialiseSharedInstance(with: config)\n\nguard let client = Comapi.shared else {\n\t\t// initialisation error\n return\n}\n// shared client ready to use", "language": "swift" } ] } [/block] # Start session Calling Comapi services requires an active session. Once you successfully initialise and retrieve a client object, call: [block:code] { "codes": [ { "code": "[client.services.session startSessionWithCompletion:^{\n // session successfully created\n} failure:^(NSError * _Nullable error) {\n // error ocurred\n}];", "language": "objectivec" }, { "code": "client.services.session.startSession(completion: { in\n // session successfully created\n}) { (error) in\n // error\n}", "language": "swift" } ] } [/block] A session will also be automatically created once you call and of the API services, so the above step is not required. To end the current session, call: [block:code] { "codes": [ { "code": "[client.services.session endSessionWithCompletion:^(CMPResult<NSNumber *> * result) {\n if (result.error) {\n // error occurred\n } else {\n BOOL success = [result.object boolValue];\n if (success) {\n // session successfully ended\n } \n }\n}];", "language": "objectivec" }, { "code": "client.services.session.endSession(completion: { result in\n if let err = result.error {\n // error occurred\n } else if let obj = result.object?.boolValue, obj == true {\n // success\n }\n})", "language": "swift" } ] } [/block] # Configuring your app to ask users' permission to send them push notifications To receive push notifications, app users must give their permission. Call the `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)` [method](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application?language=swift) to get the `deviceToken` string and pass it to the `setPushToken()` method on the `ComapiClient` object. [block:code] { "codes": [ { "code": "- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {\n NSMutableString *token = [NSMutableString string];\n \n const char *data = [deviceToken bytes];\n for (NSUInteger i = 0; i < [deviceToken length]; i++) {\n [token appendFormat:@\"%.2hhx\", data[i]];\n }\n \n if (token) {\n [client setPushToken:token completion:^(CMPResult<NSNumber *> * result) {\n \tBOOL success = [result.object boolValue];\n if (result.error || !success) {\n // error occurred\n } else {\n // configuration successful\n }\n }];\n }\n \n // rest of you push notification code\n}", "language": "objectivec" }, { "code": "func application(_ application: UIApplication,\n didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\nlet token = deviceToken.map { String(format: \"%02.2hhx\", $0) }.joined()\n//Comapi.shared gets the ComapiClient object\nComapi.shared.setPushToken(token, completion: { _ in }\n \n/* Put your custom code here */\n\n}", "language": "swift" } ] } [/block] # Displaying push notifications when the app is in the foreground Push notifications are displayed only while the app is in the background. These notification are sent to the notification center and launch your app when users tap them. If you want to display the push notification message while the app is in the foreground, do one of the following, depending on the iOS version that your app is running on: **For iOS 9** [block:code] { "codes": [ { "code": "- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {\n if (application.applicationState == UIApplicationStateActive) {\n \n }\n}", "language": "objectivec" }, { "code": "func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {\n if application.applicationState == .active {\n // Handle the notification here\n }\n}", "language": "swift" } ] } [/block] **For iOS 10 and above** [block:code] { "codes": [ { "code": "- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {\n \n completionHandler();\n}", "language": "objectivec" }, { "code": "func userNotificationCenter(_ center: UNUserNotificationCenter,\n willPresent notification: UNNotification,\n withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {\n completionHandler(.alert)\n}", "language": "swift" } ] } [/block] # References ## Adding an email address to the iOS app user's profile Push notifications can be sent from Engagement Cloud only to users who have a profile that contains an email address. To create a profile for the user, a session must be started. A session is started automatically after initialisation, but you can also start it explicitly by calling the `client.services.session.startSession()` method. When a session is started, the JWT token is used to create the user's profile ID ('profileId' string). This profile ID remains the same until a session is stopped. When a session is stopped and started again, a new JWT token is created and used to create a new profile ID. A session is stopped in any of the following circumstances: * The user uninstalls the app, and then reinstalls it * The user clears all of the app's data You can also stop a session by calling the `client.services.session.endSession()` method. [block:callout] { "type": "warning", "title": "Creating and updating users with push tokens", "body": "When you add an email address to a user's profile via the iOS SDK, a contact will automatically be created and updated with push tokens in Engagement Cloud." } [/block] [block:code] { "codes": [ { "code": "[client.services.profile patchProfileForProfileID:@\"<PROFILE-ID>\" attributes:@{@\"email\" : @\"new@email.com\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.profile.updateCurrentProfile([\"email\": \"exampleappuser1@gmail.com\"]) { result in\n // completion handler\n}", "language": "swift" } ] } [/block] [block:callout] { "type": "warning", "title": "Ask users to enter their email address", "body": "You should ask users if they'd like to receive push notifications from you and prompt them to enter their email address." } [/block] Now, when a user launches your app, the user's profile data is sent to Engagement Cloud and (if that profile contains an email address that belongs to one of your contacts) that contact can now receive push notifications from Engagement Cloud. :tada: [block:callout] { "type": "success", "body": "[Send a push notification to your app](https://support.dotdigital.com/hc/en-gb/articles/360001827870) from the program builder in Engagement Cloud.", "title": "Next steps" } [/block] ## Configuring logs You can set internal file logs and console logs to the following levels: * `verbose` * `debug` * `info` * `warning` * `error` [block:code] { "codes": [ { "code": "Log.consoleDestination.minimumLevel = .error\nLog.fileDestination.minimumLevel = .info", "language": "swift" } ] } [/block] ## Websocket Whenever ComapiClient is configured and authenticated, a new session is started which opens a WebSocket connection. This allows for a two-way real-time communication between the client and the server. For more information on WebSocket visit the official RFC site. When open, the socket provides live updates for the profile and conversations which you are a participant of. You can subscribe for these updates to update your views and models. ## Add event listener Register the delegate for the incoming events with the CMPComapiClient object. The delegate should conform to the CMPEventListener protocol. [block:code] { "codes": [ { "code": "[client addEventDelegate:self];", "language": "objectivec" }, { "code": "client.add(self)", "language": "swift" } ] } [/block] To receive the event, implement the following method from CMPEventListener protocol: [block:code] { "codes": [ { "code": "- (void)client:(CMPComapiClient *)client didReceiveEvent:(CMPEvent *)event {\n \tswitch (event.type) {\n case CMPEventTypeConversationMessageSent:\n \t\t\t// message sent event received\n break;\n case CMPEventTypeConversationParticipantTypingOff:\n \t\t\t// participant typing off event received\n break;\n case CMPEventTypeConversationParticipantTyping:\n \t\t\t// participant typing event received\n break;\n case CMPEventTypeConversationMessageRead:\n \t\t\t// message read event received\n break;\n \t// case .other events:\n default:\n \t\t\t// unknown event type\n break;\n }\n}", "language": "objectivec" }, { "code": "func client(_ client: ComapiClient, didReceive event: Event) {\n\t\tswitch event.type {\n case .conversationMessageSent:\n // message sent event received\n case .conversationParticipantTypingOff:\n // participantTypingOff event received\n case .conversationParticipantTyping:\n // participantTyping event received\n case .conversationMessageRead:\n \t\t// message read event received\n // case .other events:\n \n default:\n // unknown event type\n\t\t}\n}", "language": "swift" } ] } [/block] Please note that you can register multiple listeners in your code to handle events in different parts of your application. The events are broadcasted to all registered delegates. [block:code] { "codes": [ { "code": "[client removeEventDelegate:self];", "language": "objectivec" }, { "code": "client.remove(self)", "language": "swift" } ] } [/block] Register the delegate for the incoming events with the CMPComapiClient object. The delegate should conform to the CMPEventListener protocol. [block:code] { "codes": [ { "code": "[client removeEventDelegate:self];", "language": "objectivec" }, { "code": "client.remove(self)", "language": "swift" } ] } [/block] ## Available events [block:parameters] { "data": { "h-0": "Event type", "h-1": "Event subtype", "0-0": "profile", "0-1": "update", "0-2": "Sent when a user's profile is updated.", "1-0": "conversation", "1-1": "participantAdded", "1-2": "Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.", "2-0": "conversation", "2-1": "participantUpdated", "2-2": "Sent when a participant role is updated in a conversation.", "3-0": "conversation", "3-1": "participantRemoved", "3-2": "Sent when a participant is removed from a conversation.", "4-0": "conversation", "4-1": "delete", "4-2": "Sent when a conversation is deleted.", "5-0": "conversation", "5-1": "update", "5-2": "Sent when a conversation's details were updated.", "6-0": "conversation", "6-1": "undelete", "6-2": "Sent when a conversation is restored.", "7-0": "conversation", "7-1": "create", "7-2": "Sent when a new conversation was created.", "8-0": "conversation", "8-1": "participantTyping", "8-2": "Sent when one of the participants is typing a new message.", "9-0": "conversation", "9-1": "participantTypingOff", "9-2": "Sent when the other participant stops typing.", "10-0": "conversationMessage", "10-1": "delivered", "10-2": "Sent when on of the participants updated the message status to 'delivered'.", "11-0": "conversationMessage", "11-1": "read", "11-2": "Sent when one of the participants updated the message status to 'read'.", "12-0": "conversationMessage", "12-1": "sent", "12-2": "Sent when a new message appeared in a conversation. This event will also be delivered to the message sender." }, "cols": 3, "rows": 13 } [/block] For all events you can access id, apiSpace and name and context properties. For other events, there are other properties you can access, as follows: [block:code] { "codes": [ { "code": "profile.profileId", "language": "text", "name": "profile" } ] } [/block] ##Conversation Below are listed properties specific for the conversation event subtypes. [block:code] { "codes": [ { "code": "conversation.conversationId\nconversation.payload.profileId\nconversation.payload.role", "language": "text", "name": "participantAdded" }, { "code": "conversation.conversationId\nconversation.payload.profileId\nconversation.payload.role", "language": "text", "name": "participantUpdated" }, { "code": "conversation.conversationId\nconversation.payload.profileId\nconversation.payload.role", "language": "text", "name": "participantRemoved" }, { "code": "conversation.conversationId\nconversation.payload.date", "language": "text", "name": "delete" }, { "code": "conversation.profileId\nconversation.payload.roles\nconversation.payload.isPublic\nconversation.payload.participants", "language": "text", "name": "undelete" }, { "code": "conversation.conversationId\nconversation.payload.description\nconversation.payload.roles", "language": "text", "name": "update" }, { "code": "conversation.profileId\nconversation.payload.roles\nconversation.payload.isPublic\nconversation.payload.participants", "language": "text", "name": "create" }, { "code": "conversation.account\nconversation.payload.profileId\ncovenrsation.payload.conversationId", "language": "text", "name": "participantTyping" }, { "code": "conversation.account\nconversation.payload.profileId\ncovenrsation.payload.conversationId", "language": "text", "name": "participantTypingOff" } ] } [/block] ConversationMessage [block:code] { "codes": [ { "code": "conversationMessage.messageId\nconversationMessage.conversationId\nconversationMessage.profileId\nconversationMessage.timestamp", "language": "text", "name": "delivered" }, { "code": "conversationMessage.messageId\nconversationMessage.conversationId\nconversationMessage.profileId\nconversationMessage.timestamp", "language": "text", "name": "sent" }, { "code": "conversationMessage.messageId\nconversationMessage.metadata\nconversationMessage.parts\nconversationMessage.alert", "language": "text", "name": "read" } ] } [/block] ## Client APIs You can use **CMPComapi** client instance obtained in Initialisation to access SDK APIs. ##Session Get the current **profileID**: [block:code] { "codes": [ { "code": "BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];", "language": "objectivec" }, { "code": "let profileId = client.profileID", "language": "swift" } ] } [/block] Check if the session was successfully created: [block:code] { "codes": [ { "code": "BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];", "language": "objectivec" }, { "code": "let isSessionSuccesfullyCreated = client.isSessionSuccessfullyCreated", "language": "swift" } ] } [/block] ##Services Accessing services is done by calling methods from the proper group: [block:code] { "codes": [ { "code": "client.services.profile\nclient.services.session\nclient.services.messaging", "language": "objectivec" }, { "code": "client.services.profile\nclient.services.session\nclient.services.messaging", "language": "swift" } ] } [/block] Most services use CMPResult object as a return value in completion blocks: [block:code] { "codes": [ { "code": "@interface CMPResult<__covariant T> : NSObject\n\n@property (nonatomic, nullable) T object;\n@property (nonatomic, nullable) NSError *error;\n@property (nonatomic, nullable) NSString *eTag;\n@property (nonatomic) NSInteger code;\n\n- (instancetype)init NS_UNAVAILABLE;\n\n- (instancetype)initWithObject:(nullable T)object error:(nullable NSError *)error eTag:(nullable NSString *)eTag code:(NSInteger)code;\n\n@end", "language": "objectivec" }, { "code": "open class Result<T> : NSObject where T : AnyObject {\n\n open var object: T?\n open var error: Error?\n open var eTag: String?\n open var code: Int\n\n public init(object: T?, error: Error?, eTag: String?, code: Int)\n}", "language": "swift" } ] } [/block] You can get the ETage value from it, as well as an HTTP status code and potential error. ##Profile services Get profile: [block:code] { "codes": [ { "code": "[client.services.profile getProfileForProfileID:@\"<PROFILE-ID>\" completion:^(CMPResult<CMPProfile *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec", "name": null }, { "code": "client.services.profile.getProfile(profileID: \"\") { (result) in\n if let err = result.error {\n // error occurred\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Query profiles with query builder: [block:code] { "codes": [ { "code": "[client.services.profile queryProfilesWithQueryElements:@[] completion:^(CMPResult<NSArray<CMPProfile *> *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.profile.queryProfiles(queryElements: [.init(key: \"email\", element: .contains, value: \"new\")]) { (result) in\n if let err = result.error {\n // error occurred\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Patch profile: [block:code] { "codes": [ { "code": "[client.services.profile patchProfileForProfileID:@\"<PROFILE-ID>\" attributes:@{@\"email\" : @\"new@email.com\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.profile.patchProfile(profileID: \"profileId\", attributes: [\"email\" : \"new@email.com\"], eTag: nil) { (result) in\n if let err = result.error {\n // error occurred\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Update profile: [block:code] { "codes": [ { "code": "[client.services.profile updateProfileForProfileID:@\"<PROFILE-ID>\" attributes:@{@\"email\" : @\"new@email.com\"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.profile.updateProfile(profileID: \"profileId\", attributes: [\"email\" : \"new@email.com\"], eTag: nil) { (result) in\n if let err = result.error {\n // error occurred\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] ##Messaging services Create conversation: [block:code] { "codes": [ { "code": "CMPRoleAttributes *ownerAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:YES canRemoveParticipants:YES];\n\n CMPRoleAttributes *participantAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:NO canRemoveParticipants:NO];\n\n CMPRoles *roles = [[CMPRoles alloc] initWithOwnerAttributes:ownerAttributes participantAttributes:participantAttributes];\n\nCMPConversationParticipant *owner = [[CMPConversationParticipant alloc] initWithID:profileID role:@\"owner\"];\n\nCMPNewConversation *newConversation = [[CMPNewConversation alloc] initWithID:@\"{id}\" name:@\"{name}\" description:@\"{description}\" roles:roles participants:@[owner] isPublic:@(NO)];\n\n[client.services.messaging addConversationWithConversation:weakSelf.conversation completion:^(CMPResult<CMPConversation *> *result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "let participant = ConversationParticipant(id: \"{participantId}\", role: .participant)\nlet ownerAttributes = RoleAttributes(canSend: true, canAddParticipants: true, canRemoveParticipants: true)\nlet participantAttributes = RoleAttributes(canSend: true, canAddParticipants: false, canRemoveParticipants: false)\nlet roles = Roles(ownerAttributes: ownerAttributes, participantAttributes: participantAttributes)\nlet newConversation = NewConversation(id: \"{id}\", name: \"{name}\", description: \"{description}\", roles: roles, participants: [participant], isPublic: NSNumber.init(booleanLiteral: false))\n\nclient.services.messaging.addConversation(conversation: newConversation) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] ##Query Query all conversations: [block:code] { "codes": [ { "code": "[_client.services.messaging getConversationsWithProfileID:_client.profileID isPublic:YES completion:^(CMPResult<NSArray<CMPConversation *> *> * _Nonnull) {\n if (result.error) {\n \t// error occurred\n } else {\n \t// success\n } \n}];", "language": "objectivec" }, { "code": "client.services.messaging.getConversations(profileID: client.profileID!, isPublic: true) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.compactMap({ $0 as? Profile }) {\n // success\n }\n}", "language": "swift" } ] } [/block] Query specific conversation: [block:code] { "codes": [ { "code": "[client.services.messaging getConversationWithConversationID:@\"{id}\" completion:^(CMPResult<CMPConversation *> *result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.getConversation(conversationID: \"{id}\") { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Update conversation: [block:code] { "codes": [ { "code": "CMPConversationUpdate *update = [[CMPConversationUpdate alloc] initWithID:@\"{id}\" name:@\"{name}\" description:@\"{description}\" roles:roles isPublic:@(NO)];\n\n[client.services.messaging updateConversationWithConversationID:@\"{id}\" conversation:update eTag:nil completion:^(CMPResult<CMPConversation *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "let ownerAttributes = RoleAttributes(canSend: true, canAddParticipants: true, canRemoveParticipants: true)\nlet participantAttributes = RoleAttributes(canSend: true, canAddParticipants: false, canRemoveParticipants: false)\nlet roles = Roles(ownerAttributes: ownerAttributes, participantAttributes: participantAttributes)\nlet update = ConversationUpdate(id: \"{id}\", name: \"{name}\", description: \"{description}\", roles: roles, isPublic: NSNumber.init(booleanLiteral: false))\n \nclient.services.messaging.updateConversation(conversationID: \"{id}\", conversation: update, eTag: nil) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Delete conversation: [block:code] { "codes": [ { "code": "[client.services.messaging deleteConversationWithConversationID:@\"{id}\" eTag:nil completion:^(CMPResult<NSNumber *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.deleteConversation(conversationID: \"{id}\", eTag: nil) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.boolValue, obj == true {\n // success\n }\n}", "language": "swift" } ] } [/block] ##Participants Get participants: [block:code] { "codes": [ { "code": "[client.services.messaging getParticipantsWithConversationID:@\"{id}\" completion:^(CMPResult<NSArray<CMPConversationParticipant *> *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.getParticipants(conversationID: \"{id}\") { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.compactMap({ $0 as ConversationParticipant }) {\n // success\n }\n}", "language": "swift" } ] } [/block] Add participants: [block:code] { "codes": [ { "code": "NSArray<CMPConversationParticipant *> *participants = @[[[CMPConversationParticipant alloc] initWithID:@\"@{id1}\" role:@\"{role}\"], [[CMPConversationParticipant alloc] initWithID:@\"@{id2}\" role:@\"{role}\"]];\n\n[client.services.messaging addParticipantsWithConversationID:@\"{id}\" participants:participants completion:^(CMPResult<NSNumber *> * result) {\n if (result.error) {\n \t\t// error occurred\n } else {\n \t// success\n }\n}];", "language": "objectivec" }, { "code": "let participant = ConversationParticipant(id: \"{participantId}\", role: .participant)\n \nclient.services.messaging.addParticipants(conversationID: \"{id}\", participants: []) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.boolValue, obj == true {\n // success\n }\n}", "language": "swift" } ] } [/block] Remove participants: [block:code] { "codes": [ { "code": "[client.services.messaging removeParticipantsWithConversationID:@\"{id}\" participants:participants completion:^(CMPResult<NSNumber *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "let participant = ConversationParticipant(id: \"{participantId}\", role: .participant)\n \nclient.services.messaging.removeParticipants(conversationID: \"{id}\", participants: [participant]) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.boolValue, obj == true {\n // success\n }\n}", "language": "swift" } ] } [/block] ##Messages Query messages: [block:code] { "codes": [ { "code": "[client.services.messaging getMessagesWithConversationID:@\"{id}\" limit:100 from:0 completion:^(CMPResult<CMPGetMessagesResult *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.getMessages(conversationID: \"{id}\") { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Create message: [block:code] { "codes": [ { "code": "CMPMessagePart *part = [[CMPMessagePart alloc] initWithName:@\"{name}\" type:@\"{type}\" url:nil data:@\"{data}\" size:@(123)];\n\nCMPMessageAlert *alert = [[CMPMessageAlert alloc] initWithPlatforms:[[CMPMessageAlertPlatforms alloc] initWithApns:@{} fcm:@{}]];\n \nCMPSendableMessage *message = [[CMPSendableMessage alloc] initWithMetadata:@{} parts:@[part] alert:alert];\n\n[client.services.messaging sendMessage:message toConversationWithID:@\"{id}\" completion:^(CMPResult<CMPSendMessagesResult *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "let text = \"some message\"\nlet part = MessagePart(name: \"part1\", type: \"plain/text\", url: nil, data: text, size: NSNumber(integerLiteral: text.count))\nlet msg = SendableMessage(metadata: [\"custom field\" : \"custom value\"], parts: [part], alert: nil)\n \nclient.services.messaging.send(message: msg, conversationID: \"{id}\") { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object {\n // success\n }\n}", "language": "swift" } ] } [/block] Update messages status: [block:code] { "codes": [ { "code": "[client.services.messaging updateStatusForMessagesWithIDs:@[@\"{id1}\", @\"{id2}\"] status:@\"read\" conversationID:@\"{id}\" timestamp:[NSDate date] completion:^(CMPResult<NSNumber *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.updateStatus(messageIDs: [\"{id1}\", \"{id2}\"], status: .delivered, conversationID: \"{id}\", timestamp: Date()) { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.boolValue, obj == true {\n // success\n }\n}", "language": "swift" } ] } [/block] ##Events Query events: [block:code] { "codes": [ { "code": "[client.services.messaging queryEventsWithConversationID:@\"{id}\" limit:0 from:100 completion:^(CMPResult<NSArray<CMPEvent *> *> * result) {\n if (result.error) {\n // error occurred\n } else {\n // success\n }\n}];", "language": "objectivec" }, { "code": "client.services.messaging.queryEvents(conversationID: \"{id}\") { (result) in\n if let err = result.error {\n // error\n } else if let obj = result.object?.compactMap({ $0 as? Event }) {\n // success\n }\n}", "language": "swift" } ] } [/block]