dotdigital Engagement Cloud

The dotdigital Engagement Cloud developer hub

Official dotdigital Engagement Cloud APIs documentation

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.

Our iOS SDK uses the Apple Push Notification Service (APNS) to send push notifications to your contacts.

Our iOS SDK is open source and can be found in Github below:

To set up push notifications for native iOS apps, complete the following tasks:

  1. Install the iOS SDK
  2. Configure the iOS SDK
  3. Initialise the iOS SDK

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
# other podfile info

target '*Your-Target*'
use_frameworks!

pod 'CMPComapiFoundation'

end
  1. Install the iOS SDK
$ pod install
  1. Import the iOS SDK in your Objective-C or Swift file
#import <CMPComapiFoundation/CMPComapiFoundation.h>

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:

NSString *id = <Portal's authentication tab ID claim value>;
NSString *issuer = <Portal's authentication tab issuer value>;
NSString *audience = <Portal's authentication tab audience value>;
NSString *secret = <Portal's authentication tab secret value>;
  
- (void)client:(CMPComapiClient *)client didReceiveAuthenticationChallenge:(CMPAuthenticationChallenge *)challenge completion:(void (^)(NSString * _Nullable))continueWithToken {
  	// request a JWT token from your provider (backend server)
  	// example call
  	[YourProviderServer getTokenForNonce:challenge.nonce id:id issuer:issuer audience:audience secret:secret completion:^(NSString * token, NSError * error) {
    		// call continueWithToken block with generated token
        if (token && !error) {
            continueWithToken(token);
        }
    }];
}

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.

The JWT needs to include claims from the authentication panel in the dashboard. For further guidance, please click here.

Here's an example implementation of a token generator in Objective-C using JWT:

#import "CMPAuthenticationManager.h"
#import <JWT/JWT.h>

@implementation CMPAuthenticationManager

+ (NSString *)generateTokenForNonce:(NSString *)nonce profileID:(NSString *)profileID issuer:(NSString *)issuer audience:(NSString *)audience secret:(NSString *)secret {
    NSDate *now = [NSDate date];
    NSDate *exp = [NSCalendar.currentCalendar dateByAddingUnit:NSCalendarUnitDay value:30 toDate:now options:0];
    
    NSDictionary *headers = @{@"typ" : @"JWT"};
    NSDictionary *payload = @{@"nonce" : nonce,
                               @"sub" : profileID,
                               @"iss" : issuer,
                               @"aud" : audience,
                               @"iat" : [NSNumber numberWithDouble:now.timeIntervalSince1970],
                               @"exp" : [NSNumber numberWithDouble:exp.timeIntervalSince1970]};
    
    NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];
    id<JWTAlgorithm> algorithm = [JWTAlgorithmFactory algorithmByName:@"HS256"];
    
    NSString *token = [JWTBuilder encodePayload:payload].headers(headers).secretData(secretData).algorithm(algorithm).encode;
    return token;
}

@end
  
/* Note that this should preferably be generated by your backend, the app should only retreive the token through an HTTP call */

Configuring the iOS SDK

Before you can configure the iOS SDK, you need the following:

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

// create a config object with your api-space-id and an object conforming to CMPAuthenticationDelegate protocol;
CMPComapiConfig *config = [[CMPComapiConfig alloc] initWithApiSpaceID:@"<API_SPACE_ID>" authenticationDelegat:<CMPAuthenticationDelegate_Conforming_Object>];

CMPComapiClient *client = [CMPComapi initialiseWithConfig:config];
// we can use the client object now

Retrieving the client

The client can be retrieved either as a separate object using:

CMPComapiClient *client = [CMPComapi initialiseWithConfig:config];
// client instance ready to use

or as a singleton:

[CMPComapi initialiseSharedInstanceWithConfig:config];

CMPComapiClient *client = [Comapi shared];
// shared client ready to use

Start session

Calling Comapi services requires an active session. Once you successfully initialise and retrieve a client object, call:

[client.services.session startSessionWithCompletion:^{
  // session successfully created
} failure:^(NSError * _Nullable error) {
  // error ocurred
}];

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:

[client.services.session endSessionWithCompletion:^(CMPResult<NSNumber *> * result) {
    if (result.error) {
      // error occurred
    } else {
      BOOL success = [result.object boolValue];
      if (success) {
        // session successfully ended
      }     
    }
}];

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 to get the deviceToken string and pass it to the setPushToken() method on the ComapiClient object.

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSMutableString *token = [NSMutableString string];
    
    const char *data = [deviceToken bytes];
    for (NSUInteger i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%.2hhx", data[i]];
    }
  
    if (token) {
        [client setPushToken:token completion:^(CMPResult<NSNumber *> * result) {
          	BOOL success = [result.object boolValue];
            if (result.error || !success) {
                // error occurred
            } else {
                // configuration successful
            }
        }];
    }
    
    // rest of you push notification code
}

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

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    if (application.applicationState == UIApplicationStateActive) {
        
    }
}

For iOS 10 and above

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
    
    completionHandler();
}

References

Next steps

Now ensure your app passes an email address to the SDK for the app user to ensure they get a contact created in Engagement Cloud by following these instructions

Configuring logs

You can set internal file logs and console logs to the following levels:

  • verbose
  • debug
  • info
  • warning
  • error

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.

[client addEventDelegate:self];

To receive the event, implement the following method from CMPEventListener protocol:

- (void)client:(CMPComapiClient *)client didReceiveEvent:(CMPEvent *)event {
  	switch (event.type) {
        case CMPEventTypeConversationMessageSent:
      			// message sent event received
            break;
        case CMPEventTypeConversationParticipantTypingOff:
      			// participant typing off event received
            break;
        case CMPEventTypeConversationParticipantTyping:
      			// participant typing event received
            break;
        case CMPEventTypeConversationMessageRead:
      			// message read event received
            break;
      	// case .other events:
        default:
      			// unknown event type
            break;
    }
}

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.

[client removeEventDelegate:self];

Register the delegate for the incoming events with the CMPComapiClient object. The delegate should conform to the CMPEventListener protocol.

[client removeEventDelegate:self];

Available events

Event type
Event subtype

profile

update

Sent when a user's profile is updated.

conversation

participantAdded

Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.

conversation

participantUpdated

Sent when a participant role is updated in a conversation.

conversation

participantRemoved

Sent when a participant is removed from a conversation.

conversation

delete

Sent when a conversation is deleted.

conversation

update

Sent when a conversation's details were updated.

conversation

undelete

Sent when a conversation is restored.

conversation

create

Sent when a new conversation was created.

conversation

participantTyping

Sent when one of the participants is typing a new message.

conversation

participantTypingOff

Sent when the other participant stops typing.

conversationMessage

delivered

Sent when on of the participants updated the message status to 'delivered'.

conversationMessage

read

Sent when one of the participants updated the message status to 'read'.

conversationMessage

sent

Sent when a new message appeared in a conversation. This event will also be delivered to the message sender.

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:

profile.profileId

Conversation

Below are listed properties specific for the conversation event subtypes.

conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.date
conversation.profileId
conversation.payload.roles
conversation.payload.isPublic
conversation.payload.participants
conversation.conversationId
conversation.payload.description
conversation.payload.roles
conversation.profileId
conversation.payload.roles
conversation.payload.isPublic
conversation.payload.participants
conversation.account
conversation.payload.profileId
covenrsation.payload.conversationId
conversation.account
conversation.payload.profileId
covenrsation.payload.conversationId

ConversationMessage

conversationMessage.messageId
conversationMessage.conversationId
conversationMessage.profileId
conversationMessage.timestamp
conversationMessage.messageId
conversationMessage.conversationId
conversationMessage.profileId
conversationMessage.timestamp
conversationMessage.messageId
conversationMessage.metadata
conversationMessage.parts
conversationMessage.alert

Client APIs

You can use CMPComapi client instance obtained in Initialisation to access SDK APIs.

Session

Get the current profileID:

BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];

Check if the session was successfully created:

BOOL isSuccessfullyCreated = [client isSessionSuccessfullyCreated];

Services

Accessing services is done by calling methods from the proper group:

client.services.profile
client.services.session
client.services.messaging

Most services use CMPResult object as a return value in completion blocks:

@interface CMPResult<__covariant T> : NSObject

@property (nonatomic, nullable) T object;
@property (nonatomic, nullable) NSError *error;
@property (nonatomic, nullable) NSString *eTag;
@property (nonatomic) NSInteger code;

- (instancetype)init NS_UNAVAILABLE;

- (instancetype)initWithObject:(nullable T)object error:(nullable NSError *)error eTag:(nullable NSString *)eTag code:(NSInteger)code;

@end

You can get the ETage value from it, as well as an HTTP status code and potential error.

Profile services

Get profile:

[client.services.profile getProfileForProfileID:@"<PROFILE-ID>" completion:^(CMPResult<CMPProfile *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Query profiles with query builder:

[client.services.profile queryProfilesWithQueryElements:@[] completion:^(CMPResult<NSArray<CMPProfile *> *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Patch profile:

[client.services.profile patchProfileForProfileID:@"<PROFILE-ID>" attributes:@{@"email" : @"[email protected]"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Update profile:

[client.services.profile updateProfileForProfileID:@"<PROFILE-ID>" attributes:@{@"email" : @"[email protected]"} eTag:nil completion:^(CMPResult<CMPProfile *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Messaging services

Create conversation:

CMPRoleAttributes *ownerAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:YES canRemoveParticipants:YES];

    CMPRoleAttributes *participantAttributes = [[CMPRoleAttributes alloc] initWithCanSend:YES canAddParticipants:NO canRemoveParticipants:NO];

    CMPRoles *roles = [[CMPRoles alloc] initWithOwnerAttributes:ownerAttributes participantAttributes:participantAttributes];

CMPConversationParticipant *owner = [[CMPConversationParticipant alloc] initWithID:profileID role:@"owner"];

CMPNewConversation *newConversation = [[CMPNewConversation alloc] initWithID:@"{id}" name:@"{name}" description:@"{description}" roles:roles participants:@[owner] isPublic:@(NO)];

[client.services.messaging addConversationWithConversation:weakSelf.conversation completion:^(CMPResult<CMPConversation *> *result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Query

Query all conversations:

[_client.services.messaging getConversationsWithProfileID:_client.profileID isPublic:YES completion:^(CMPResult<NSArray<CMPConversation *> *> * _Nonnull) {
    if (result.error) {
      	// error occurred
    } else {
      	// success
    }              
}];

Query specific conversation:

[client.services.messaging getConversationWithConversationID:@"{id}" completion:^(CMPResult<CMPConversation *> *result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Update conversation:

CMPConversationUpdate *update = [[CMPConversationUpdate alloc] initWithID:@"{id}" name:@"{name}" description:@"{description}" roles:roles isPublic:@(NO)];

[client.services.messaging updateConversationWithConversationID:@"{id}" conversation:update eTag:nil completion:^(CMPResult<CMPConversation *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Delete conversation:

[client.services.messaging deleteConversationWithConversationID:@"{id}" eTag:nil completion:^(CMPResult<NSNumber *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Participants

Get participants:

[client.services.messaging getParticipantsWithConversationID:@"{id}" completion:^(CMPResult<NSArray<CMPConversationParticipant *> *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Add participants:

NSArray<CMPConversationParticipant *> *participants = @[[[CMPConversationParticipant alloc] initWithID:@"@{id1}" role:@"{role}"], [[CMPConversationParticipant alloc] initWithID:@"@{id2}" role:@"{role}"]];

[client.services.messaging addParticipantsWithConversationID:@"{id}" participants:participants completion:^(CMPResult<NSNumber *> * result) {
    if (result.error) {
    		// error occurred
    } else {
      	// success
    }
}];

Remove participants:

[client.services.messaging removeParticipantsWithConversationID:@"{id}" participants:participants completion:^(CMPResult<NSNumber *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Messages

Query messages:

[client.services.messaging getMessagesWithConversationID:@"{id}" limit:100 from:0 completion:^(CMPResult<CMPGetMessagesResult *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Create message:

CMPMessagePart *part = [[CMPMessagePart alloc] initWithName:@"{name}" type:@"{type}" url:nil data:@"{data}" size:@(123)];

CMPMessageAlert *alert = [[CMPMessageAlert alloc] initWithPlatforms:[[CMPMessageAlertPlatforms alloc] initWithApns:@{} fcm:@{}]];
    
CMPSendableMessage *message = [[CMPSendableMessage alloc] initWithMetadata:@{} parts:@[part] alert:alert];

[client.services.messaging sendMessage:message toConversationWithID:@"{id}" completion:^(CMPResult<CMPSendMessagesResult *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Update messages status:

[client.services.messaging updateStatusForMessagesWithIDs:@[@"{id1}", @"{id2}"] status:@"read" conversationID:@"{id}" timestamp:[NSDate date] completion:^(CMPResult<NSNumber *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Events

Query events:

[client.services.messaging queryEventsWithConversationID:@"{id}" limit:0 from:100 completion:^(CMPResult<NSArray<CMPEvent *> *> * result) {
    if (result.error) {
        // error occurred
    } else {
        // success
    }
}];

Updated 9 minutes ago

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.

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.