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.

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 where
    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
o 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
o Less if Bitcode-enabled
▪ Content push data usage: 100kb or less

Level of Effort

Task

Skillset

Time of Effort

Install CocoaPods

Linux

10 min

Install Artifactory plugin

Linux

10 min

Configure standard netric file to authenticate the First Orion Artifactory

Linux

5 min

Configure Podfile to resolve Push Library dependency

CocoaPods

10 min

Configure app requirements in Xcode and Apple Developer Portal

Xcode / Apple Developer Portal

25 min

Initialize and configure Push Library SDK in host app

Swift

60 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.

Field

Type

Description

pushLibHost

String

Base URL for communicating with ENGAGE platform

clientId

String

Identity credentials used for retrieving Authorization Token

clientSecret

String

Identity credentials used for retrieving Authorization Token

serviceProviderUuid

String

Service Provider UUID associated with integration
customer and business pushing ENGAGE content

keychainAccessGroup

String

Adding 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.

Setting up Keychain SharingSetting up Keychain Sharing

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

Confirm Keychain Group matches Bundle IdentifierConfirm Keychain Group matches Bundle Identifier

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.

Verify PushLib has Access to KeychainVerify PushLib has Access to Keychain

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:

Field

Example

Keychain Group

com.firstorion.engage.PushLibGoldCopy

TeamID

UOMHJOJI6Y

KeychainAccessGroup

UOMHJOJI6Y.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 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 FIrstorion 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 CocaoPods

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, as shown below:

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:

Parameter

Type

Description

forEnvironment

PushLibEnvironment (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

completion

Closure (optional)

Takes a single argument, an optional PushLibError
It is nil if no error occured

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 Value

Description

test

Test environment
Should be used with any non-production environment by the host app

production

Test 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 Value

Description

notConfigured

Push Library is not configured
Host app needs to ensure it is calling configurePushLib, probably
from AppDelegate’s didFinishLaunchingWithOptions
Could be caused by configurationFailure

configurationFailure

Push 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

deviceIdMissing

There 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

deviceIdGenerationFailure

A 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

deviceIdStorageFailure

A 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

deviceIdRetrievalFailure

The device ID could not be retrieved from the keychain
The same action should be taken for the host app as in
deviceIdStorageFailure

contentProviderError

The 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

aNumberFormatError

The 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

bNumberFormatError

The 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

sendPushNetworkError

The 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

sendPushPlatformError

The 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.

Field

Type

Description

httpStatusCode

Integer

HTTP status code of the response

responseBody

String (optional)

Body (JSON, if present) of the response

PushLibPlatofrmResponse

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

Field

Type

Description

response

String (optional)

A shorthand representation of the failure

message

String (optional)

An explanation of the failure

errorCode

Integer (optional)

l) Represents the type of error that occurred

requestId

String (optional)

An identifier for the failed request to send push

Retreive 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)")
                }
        }
}

Parameter

Type

Description

completion

Closure

When 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.

Field

Type

Description

bane

String

Name of the preferred content program

uuid

String

Unique 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 below:

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 Property

Type

Required

Description

bNumber

String

Yes

Device number that will display program
content when call is received after a
successful push.
Must be E. 164 format

customData

Dictionary ([String:String])

No

Custom 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

timeToLiveSeconds

Integer

No

Duration, 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

keepAfterCall

Boolean

No

Set 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

maxWaitSecond

Integer

No

Max wait time, in seconds, for response
from the device, to the handset, to
pushing content
Recommended value: 10 seconds

aNumbers

[String]

Yes

Originating numbers calling a bNumber.
aNumber must be previously uploaded
in the Call Enhancement Content Portal.
List of up to 5,000 aNumbers in E. 164
format. Cannot send UUID