iOS Push Library

iOS Push Library

Overview

The iOS Push Library is intended to securely deliver ENGAGE® content from an iOS mobile application securely to an ENGAGE-enabled device. The Library provides the secure functionality to Push ENGAGE content by using an OAuth authentication scheme and setting the provided clientID and clientSecret in the pushlib-config.json file of the host app.

Library Version History

VersionDateChanges
1.015-July-2020Initial release for ENGAGE iOS Client Push Library

Pre-Integration Steps

📘

First Orion will provide the following during pre-integration:

clientId/clientSecret – identity credentials used to retrieve a valid Authorization Token for securely pushing ENGAGE content

First Orion Artifactory Credentials – required for accessing the CocoaPods repository in which the Push Library is published, in the First Orion artifact repository

pushlib-config.json – configuration file for the host app to communicate with the ENGAGE platform

📘

Integration Customer will provide First Orion:

Communication – notify the First Orion team as soon as possible if the ENGAGE-enabled host app will be an iOS application so First Orion may deliver all required configuration

Skills Required

  • Apple Developer Portal
  • Linux
  • Swift
  • Xcode
  • CocoaPods

System Requirements

  • MacOS
  • Ruby >= 2.0
  • CocoaPods >= 1.10
  • CocoaPods Plugin to work with Artifactory >= 1.0.5
  • Xcode >= 10
  • Swift 4 or 5

Host App Requirements

  • iOS 10.x or higher
  • Ability to link Apple Dynamic Frameworks
  • Swift (4 or 5) or Objective-C
  • Provisioning Profiles
    • Host App

🚧

Cross-Platform Compatability

Cross-platform mobile frameworks are not supported or compatible with ENGAGE SDK

See the Cross-Platform Framework section for more detail

Host App Impact

  • SDK size: ~1mb
    • Less if Bitcode-enabled
  • Content push data usage: 100kb or less

Level of Effort

TaskSkillsetTime of Effort
Install CocoaPodsLinux10 min
Install Artifactory pluginLinux10 min
Configure standard netric file to authenticate the First Orion ArtifactoryLinux5 min
Configure Podfile to resolve Push Library dependencyCocoaPods10 min
Configure app requirements in Xcode and Apple Developer PortalXcode / Apple Developer Portal25 min
Initialize and configure Push Library SDK in host appSwift60 min
Test Library integration steps:
Update configuration file with credentials and Service Provider UUID
Push ENGAGE content from device using Service Provider approved aNumber to ENGAGE-enabled
bNumber
Check Contact created on ENGAGE-enabed bNumber device
Make phone call to device using aNumber that was pushed to ENGAGE-enabled bNumber device
20 min

Configure pushlib-config.json

The pushlib-config.json file defines the configuration settings that enable network communication with the ENGAGE platform. The only step required is to include this file in the main target of the host app Xcode project. First Orion will provide you with a configuration file before integration efforts begin.

Example pushlib-config.json

{
    "test": {
 	      "pushLibHost" : "api.fo-engage.com",
        "clientId" : "2256njcldf6u6dh4r55gc06huz",
        "clientSecret" :"97hieqqt5eip53ir92he9aa23ji1dhau9uts0do6pi4lbhqgoojd",
        "serviceProviderUuid": "13f68f49-39e5-48de-9e4a-1b6e259e10a5",
        "keychainAccessGroup": "X3RCY6K2JW.com.firstorion.engage.pushlib"
    },
    "production": {
        "pushLibHost" : "api.fo-engage.com",
        "clientId" : "2256njcldf6u6dh4r55gc06huz",
        "clientSecret" :"97hieqqt5eip53ir92he9aa23ji1dhau9uts0do6pi4lbhqgoojd",
        "serviceProviderUuid": "f5cfc9ca-5968-45a0-b2a5-69a0ebe4beb7",
        "keychainAccessGroup": "X3RCY6K2JW.com.firstorion.engage.pushlib"
    }
 }

pushlib-config.json fields

The following fields are the required configuration parameters in the config file.

🚧

NOTE

There are two objects, test and production, that each contain all of these fields. These define the separate configurations for each environment of Push Library.

FieldTypeDescription
pushLibHostStringBase URL for communicating with ENGAGE platform
clientIdStringIdentity credentials used for retrieving Authorization Token
clientSecretStringIdentity credentials used for retrieving Authorization Token
serviceProviderUuidStringService Provider UUID associated with integration customer and business pushing ENGAGE content
keychainAccessGroupStringAdding the Keychain dependency requires sharing the host app Group ID with the Push Lib. This is required and can be found in Xcode under Signing & Capabilities of the host app

Xcode Application Setup

The required Xcode configuration and impact is minimal. The most important step is ensuring that the host app has the Keychain Sharing capability properly configured.

Keychain Sharing

ENGAGE Platform requires a way to map requests made via PushLib to a particular device.

PushLib handles this by creating a unique identifier for the device, sending the identifier generated during the authentication request, before making other requests to the platform. This allows devices to leverage the library’s core functionality. The identifier needs to be unique and unchanging, even if the host app containing PushLib is deleted and reinstalled. For this reason, it is permanently stored in the Keychain.

Setting Up Keychain Sharing Capability

The Keychain Sharing capability must be enabled for PushLib to access the Keychain. Specifically, a Keychain Group must be added that will match the Bundle Identifier of the main target.

In Xcode, go to the project viewer that lists the targets of the app and choose the main target. Navigate to the Signing & Capabilities tab, then click on + Capability to add a capability. Search for Keychain Sharing in the window that appears and select it.

636

Setting up Keychain Sharing

After selecting Keychain Sharing, it will appear as a new section in the Signing & Capabilities view. Under the list of Keychain Groups, which at this point should be empty, click the + button to add a new Keychain Group.

Xcode should automatically create a Keychain Group with the same name as the Bundle Identifier in the same target. You may copy-paste the Bundle Identifier into the Keychain Groups entry field.

Confirm the Keychain Group matches the Bundle Identifier:

640

Confirm Keychain Group matches Bundle Identifier

Verifying PushLib Config File for Keychain Access

After the correct Keychain Group has been added, check the pushlib-config.json file’s keychainAccessGroup field (for each environment). The string value for this field should be a combination of the TeamId (not the Team name), which is set as the Team in the Xcode target, and the Keychain Group.

One way to find the TeamId is to check-in Apple Developer. Another way is to search the workspace for DEVELOPMENT_TEAM. This setting is found in the Build Settings tab in the project target view. The plain value, which is equivalent to the TeamId, is shown in the search result in the search bar on the left.

636

Verify PushLib has Access to Keychain

Once the TeamId value has been determined, verify that the keychainAccessGroup field is correct.

It is constructed as shown in this example:

FieldExample
Keychain Groupcom.firstorion.engage.PushLibGoldCopy
TeamIDUOMHJOJI6Y
KeychainAccessGroupUOMHJOJI6Y.com.firstorion.engage.PushLibGoldCopy

Resolve Library Dependencies

The ENGAGE Push Library is published in the First Orion artifact repository. Credentials to access the repository will be provided by First Orion before integration efforts begin.

❗️

These artifact repository credentials:

  • should be securely stored
  • should never be checked into a source code repository
  • should be appropriately retrieved during integration and app build processes

The suggested and preferred way to resolve the ENGAGE SDK dependencies is to access the binary from the First Orion Artifactory, which is a CocoaPod package. This particular method requires setting the correct system configurations to authenticate against Artifactory.

CocoaPod >=1.10 Requirement

The required CocoaPods version must be 1.10 or above to support the ENGAGE SDK which is compiled as an XCFramework.

Required Prerequisites

Ruby >= 2.0 installed

  • CocoaPods >= 1.9 installed
  • CocoaPods Artifactory Plugin >= 1.4 installed

Configure First Orion Artifactory

Install CocoaPods Artifactory Plugin

$ gem install cocoapods-art

Add First Orion Repository

To resolve ENGAGE pods, add First Orion repo to client

$ pod repo-art add cocoapods-local
"https://firstorion.jfrog.io/firstorion/api/pods/fo-cocoapods-external"

Authentication

To authenticate, please add the credentials to the client .netrc file

machine firstorion.jfrog.io
login <USERNAME>
password <PASSWORD>

Configure Podfile

Resolve Pods

Add First Orion repo to Podfile

plugin 'cocoapods-art', :sources => [
 'fo-cocoapods-external'
]

Configure ENGAGE Pods

Add ENGAGE pod to Main Application target

platform :ios, '10.0'
use_frameworks!
plugin 'cocoapods-art', :sources => [
 'cocoapods-local'
]
target 'PushLibGoldCopy' do
 pod 'EngagePushLib', '= 1.0.0'
end

Install Pods

$ pod install

Troubleshooting CocoaPods

When working with CocoaPods, it is sometimes necessary to remove the local Pod repo and to re-sync. This may need to be done occasionally when new SDK releases become available and the client’s local CocoaPods repo needs to re-sync. The following steps outline the removal of the First Orion Pod repo from the client and re-syncing.

Remove Pods from Artifactory Repo

$ pod repo-art remove cocoapods-local

Remove Local CocoaPods References

$ rm -rf ~/.cocoapods/repos/cocoapods-local

Remove CocoaPod Cache, not always required

$ pod cache clean --all 

Deintegrate Cocoapod, not always required

$ pod deintegrate 

Re-add First Orion CocoaPods Repo

$ pod repo-art add cocoapods-local
"https://firstorion.jfrog.io/firstorion/api/pods/cocoapods-local"

Install Pods from Podfile

From the project’s main directory – pod install could also be used

$ pod update

Push Library Usage

Import PushLib

In any source file that uses PushLib, the line import EngagePushLib must be included in the import statements for that file.

Initialize PushLib

Before the iOS application can securely deliver ENGAGE content to an ENGAGE-enabled device, the Push Library must be configured by the app. This is best accomplished in the AppDelegate class:

func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ... your app's other initialization logic
    PushLib.shared.configurePushLib(forEnvironment: .test) {
optionalPushLibError in
PushLib.shared.configurePushLib(forEnvironment: .test) { optionalPushLibError
in
            if let pushLibError = optionalPushLibError {
                switch pushLibError {
                case .notConfigured:
                    self.handleNotConfigured()
                case .configurationFailure(let _, let webResponse):
                    self.handleConfigurationFailure(webResponse: webResponse)
                case .deviceIdMissing:
                    self.handleDeviceIdMissing()
                case .deviceIdGenerationFailure:
                    self.handleDeviceIdGenerationFailure()
                case .deviceIdStorageFailure:
                    self.handleDeviceIdStorageFailure()
                case .deviceIdRetrievalFailure:
                    self.handleDeviceIdRetrievalFailure()
                default:
                    print("Unexpected PushLibError case encountered:
\(pushLibError.localizedDescription)")
               }
            } else {
                print("Push Library configuration succeeded.")
            }
        if let pushLibError = optionalPushLibError {
            print("Push Library configuration failed:
\(pushLibError.localizedDescription)")
        } else {
            print("Push Library configuration succeeded.")
        }
    }

The usage of Push Lib’s configurePushLib function is as follows:

ParameterTypeDescription
forEnvironmentPushLibEnvironment (See Following Table)Used to determine with which ENGAGE environment to interact, pulling configuration from the pushlib-config.json file. Possible values are:
.test and .production
completionClosure (optional)Takes a single argument, an optional PushLibError. It is nil if no error occurred

The configurePushLib function reads pushlib-config.json, using the values for the environment specified as the forEnvironment parameter, to set up the PushLib. It also requests an authorization token from ENGAGE to use in later network requests. Once completed, and if optional parameter has been selected, it calls completion.

🚧

The following PushLibError cases may occur within this function:

notConfigured
configurationFailure
deviceIdMissing
deviceIdGenerationFailure
deviceIdStorageFailure
deviceIdRetrievalFailure

PushLibEnvironment

The environment in which to use the Push Library. An enum type.

Enum ValueDescription
testTest environment. Should be used with any non-production environment by the host app
productionTest environment. Should be used with any non-production environment by the host app

PushLibError

PushLibError is a custom Swift Error type and is therefore an enum type.

Enum ValueDescription
notConfiguredPush Library is not configured Host app needs to ensure it is calling configurePushLib, probably from AppDelegate’s didFinishLaunchingWithOptions. Could be caused by configurationFailure
configurationFailurePush Library configuration failed Contains network property, which is an optional
PushLibWebResponse (See following table) and will be present if configuration failed due to an API or network error. Host app needs to verify that it contains a pushlib-config.json file that is formatted correctly and contains appropriate values
deviceIdMissingThere is no device ID stored in the device’s keychain Host app needs to ensure it is calling configurePushLib Device ID is also managed in this function. Could be caused by: deviceIdGenerationFailure or deviceIdStorageFailure
deviceIdGenerationFailureA device ID could not be generated for this device Host app might not be able to recover from this error. Currently, Device ID is being set to identifierForVendor

Check:
https://developer.apple.com/documentation/uikit/uidevice/1620059
-identifierforvendor
If it is absent, device ID cannot be set
deviceIdStorageFailureA device ID could not be stored on this device’s keychain Host app needs to verify that it includes in its entitlements the permission to access the keychain access group equal to the
keychainAccessGroup value in pushlib-config.json. This error might also be caused by other types of failures when attempting to store data to the keychain
deviceIdRetrievalFailureThe device ID could not be retrieved from the keychain The same action should be taken for the host app as in deviceIdStorageFailure
contentProviderErrorThe content providers could not be retrieved Contains network property, which is an optional
PushLibWebResponse (See following table) and will be present if fetching the content providers failed due to an API or network error. Host app should ensure that the serviceProviderUuid property in its pushlib-config.json file is valid
aNumberFormatErrorThe aNumbers property, defined in the PushProperties (See following table) object, was invalid The host app needs to ensure that each string value in the aNumbers array is formatted as an E.164 phone number
bNumberFormatErrorThe bNumber property, defined in the PushProperties (See following table) object, was invalid The host app needs to ensure that the bNumber string value is formatted as an E.164 phone number
sendPushNetworkErrorThe content push could not be sent due to an API or network error Contains network property, which is an optional PushLibWebResponse (See following table) and will be present if the API provided a response to the request. Host app should ensure all parameters used in the call to sendPush are valid
sendPushPlatformErrorThe content push was attempted but could not be sent due to an external condition (e.g., the bNumber is from a device that is not ENGAGE-enabled) Contains platform property, which is an optional PushLibPlatformResponse (See following table) and will be present if the API provided a response to the request. Host app should ensure it is calling sendPush with appropriate parameter values (e.g., the bNumber should be from an ENGAGE-enabled device, maxWaitSeconds should be enough to
ensure the push can be sent)

PushLibWebResponse

Represents the API response received from a request that did not succeed.

FieldTypeDescription
httpStatusCodeIntegerHTTP status code of the response
responseBodyString (optional)Body (JSON, if present) of the response

PushLibPlatformResponse

Represents the API response received from a request to send a content push that was attempted and failed to send the push.

FieldTypeDescription
responseString (optional)A shorthand representation of the failure
messageString (optional)An explanation of the failure
errorCodeInteger (optional)Represents the type of error that occurred
requestIdString (optional)An identifier for the failed request to send push

Retrieve Content Provider List

When the iOS app needs to get the list of accessible content providers, it may call the
getContentProviders function and used as shown:

PushLib.shared.getContentProviders { result in 
        switch(result) {
                case .success(let contentProviders):
                print("Successfully retrieved \(contentProviders.count) content
provider(s).")
                case .failure(let pushLibError):
      switch pushLibError {
                case .notConfigured:
                    self.handleNotConfigured()
                case .configurationFailure(let _, let webResponse):
                    self.handleConfigurationFailure(webResponse: webResponse)
                case .deviceIdMissing:
                    self.handleDeviceIdMissing()
                case .deviceIdRetrievalFailure:
                    self.handleDeviceIdRetrievalFailure()
                case .contentProviderError(let _, let webResponse):
                    self.handleContentProviderError(webResponse: webResponse)
                default:
                    print("Unexpected PushLibError case encountered:
\(pushLibError.localizedDescription)")
                }
        }
}
ParameterTypeDescription
completionClosureWhen getContentProviders is done, it calls this closure, which takes one argument of the type: Result<[ContentProvider],PushLibError>

This Result object may be unwrapped to get:
ContentProviders, if the function succeeded
Error, if it failed

🚧

The following PushLibError cases may occur within this function:

notConfigured
configurationFailure
deviceIdMissing
deviceIdRetrievalFailure
contentProviderError

ContentProvider

A content provider, which can be retrieved from the getContentProviders function.

FieldTypeDescription
contentContent (See following table)Regular content of the content provider
preferredContentContentPreferred content of the content provider
isPreferredContentBooleanWhether this content provider uses preferred content

Content

Content of a content provider.

FieldTypeDescription
baneStringName of the preferred content program
uuidStringUnique value representing preferred content program created in the portal. If this value is returned as all zeros, the entry is not considered preferred content

Push Content

When the iOS app needs to push content, it can call the sendPush function as shown:

do {
  let pushProperties = try PushProperties(aNumbers: [”+15016543210",
  ”+15011234567”], bNumber: "+15013334444")
  PushLib.shared.sendPush(withProperties: pushProperties,
programContentUuid: ”521863c3-2f10-37a2-7bcd-b268012f09a4",
contentProviderUuid:	) { optionalPushLibError in

if let pushLibError = optionalPushLibError {
  // handle any error from PushLib.shared.sendPush
  switch pushLibError {
  case .notConfigured
    self.handleNotConfigured()
  case .configurationFailure(let _, let webResponse)
    self.handleConfigurationFailure(webResponse: webResponse)
  case .deviceIdMissing
    self.handleDeviceldMissing()
  case .deviceldRetrievalFailure
    self.handleDeviceldRetrievalFailure()
  case .sendPushNetworkError(let _, let webResponse)
    self.handleSendPushNetworkError(webResponse: webResponse)
  case .sendPushPlatformError(let _, let platformResponse)
    self.handleSendPushPlatformError(platformResponse:
platformResponse)
  default:
    print("Unexpected PushLibError case encountered:
  \ (pushLibError.localizedDescription)")
  }
    } else {
      print("Pushing Content succeeded.”)
    }
  }

} catch {
  // handle any error thrown by PushProperties.init
  guard let pushLibError = error as? PushLibError else {
    print("Unexpected error encountered: \(error)")
    return
}
switch pushLibError {
case .aNumberFormatError
  handleANumberFormatError()
case .bNumberFormatError
  handleBNumberFormatError()
default:
  print("Unexpected PushLibError case encountered:
  \ (pushLibError.localizedDescription)")

   }
}

PushProperties

Properties that define a push.

📘

See Glossary for further definition of terms

Push PropertyTypeRequiredDescription
bNumberStringYesDevice number that will display program content when call is received after a successful push
Must be E.164 format
customDataDictionary ([String:String])NoCustom text sent to bNumber and displayed during call. Recommended value: 20
First Orion best practice: maximum of 20 characters as messages over 20 characters may not display correctly on all devices
timeToLiveSecondsIntegerNoDuration, in seconds, for content to live on the device following a successful content push
Default is 3600
Recommended value: 960 seconds
First Orion best practice: set the TTL to 16 minutes
keepAfterCallBooleanNoSet if content should be kept on device after call
Default is false
Recommended value: false
First Orion best practice: do not keep content on the device after a call or successful push
maxWaitSecondIntegerNoMax wait time, in seconds, for response from the device, to the handset, to pushing content
Recommended value: 10 seconds
aNumbers[String]YesOriginating numbers calling a bNumber. aNumber must be previously uploaded in the Branded Communication Content Portal
List of up to 5,000 aNumbers in E.164 format
Cannot send UUID