SDK configuration
Prerequisites
- The push notification provider SDKs are integrated into the app project (see the push provider integration instructions).
- The Altcraft package has been downloaded with Swift Package Manager or CocoaPods.
Preparing the app
Target settings
Configure the following target parameters of the app:
- General
When using Swift Package Manager, check that the Altcraft library is added to the Frameworks, Libraries, and Embedded Content of the app target.
- Signing & Capabilities
Add the following Capabilities:
- PushNotifications;
- AppGroups;
- Background Modes (Background fetch, Background processing).
- Info
Add the key Permitted background task scheduler identifiers and the value "lib.Altcraft.bgTask.systemControl". This is necessary to register the bgTask that will retry failed requests to the server in the background mode.
- Optional:
When using Firebase Cloud Messaging, add the FirebaseAppDelegateProxyEnabled (Boolean) key with the value NO. This disables the automatic method swizzling of the AppDelegate methods that Firebase Messaging performs by default.
AppDelegate settings
Next, in AppDelegate.application(_:didFinishLaunchingWithOptions:), do the following:
- pass the
AppGroupidentifier to the SDK - register the
BGTask(SDK background tasks) - (optional) initialize the
UNUserNotificationCenterfunctions on the SDK side
Setting the AppGroup identifier
The AppGroup identifier is needed to exchange information with the Notification Service Extension and is required for the SDK to work correctly. It is set with the setAppGroup() function:
AltcraftSDK.shared.setAppGroup(groupName: String)
Registering BGTask
Registering the SDK background tasks is needed to resend failed requests in the background. It is done with the registerBackgroundTask() function:
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
Initializing the UNUserNotificationCenter functions (optional)
The SDK contains the NotificationManager class, which performs the following tasks:
- configures
UNUserNotificationCenterand requests notification permission (alert/sound/badge); - ensures that
UIApplication.registerForRemoteNotifications()is called and guarantees correct registration in APNs; - displays notifications in the foreground;
- handles clicks on notifications and buttons — navigates to the specified URL or opens the app;
- registers the push open event ("open").
To use the SDK's NotificationManager class, call the function in AppDelegate.application(_:didFinishLaunchingWithOptions:):
AltcraftSDK.shared.push.notificationManager.registerForPushNotifications(for: application, completion: ((_ granted: Bool, _ error: Error?) -> Void)? = nil)
This function assigns the delegate, asks the user for permission, and registers the app to receive push notifications on the SDK side.
Example of a correct AppDelegate.application implementation
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let appGroup = "group.your.id"
//ID App Group Setup
AltcraftSDK.shared.setAppGroup(groupName: appGroup)
//Background SDK task registration
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
//SDK UNUserNotificationCenter functions initialization
AltcraftSDK.shared.push.notificationManager.registerForPushNotifications(for: application)
//Other functions
return true
}
//Other functions AppDelegate
}
Using this class is optional: you can replace it with your own implementation. To do this:
- assign the notification center delegate:
UNUserNotificationCenter.current().delegate = self; - request notification permission and call
UIApplication.shared.registerForRemoteNotifications()strictly on the main thread; - implement the
userNotificationCenter(_:willPresent:withCompletionHandler:)method to display notifications in the foreground (banner/alert/sound/badge); - call
AltcraftSDK.shared.push.pushEvents.openEvent(from:)inuserNotificationCenter(_:didReceive:withCompletionHandler:)when handling the notification tap (registering the "open" event); - implement your own logic for handling notification clicks (URL/Deep Link/buttons), and do not forget to think through a fallback scenario (the default navigation when there is no valid link).
Configuring the JWT protocol (optional)
JWTInterface is the protocol for requesting a JWT token. It provides the current JWT token from the app on the SDK's request. Implementing this protocol is required if JWT authentication of API requests is used. The JWT confirms that the user identifiers are authenticated by the app.
Implementing JWT authentication is mandatory if the matching type is something other than the push data from the subscription (for example, the user identifier is an email or phone number).
The SDK protocol:
public protocol JWTInterface {
func getToken() -> String?
}
Implementation on the app side
import Altcraft
class JWTProvider: JWTInterface {
func getToken() -> String? {
//your code that returns the JWT
}
}
Registering the provider in application(_:didFinishLaunchingWithOptions:)
In AppDelegate, specify:
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Other code
AltcraftSDK.shared.setJWTProvider(provider: JWTProvider())
}
//Other functions
}
getToken() is a synchronous function. The SDK execution flow is suspended until the JWT is received. It is recommended that getToken() return the value immediately — from a cache (in-memory, UserDefaults, or Keychain) — this speeds up request execution. It is advisable to prepare the current JWT as early as possible (at app startup) and store it in the cache so that when the SDK requests it, the token is available without delays. If no value is available, returning nil is allowed.
Protocols for requesting and deleting the provider push token
This implementation of the request and deletion protocols for the push token guarantees that the current token is used and enables dynamic provider switching on the client's demand. Implement the protocols only for the push providers used in your project.
Register the providers in application(_:didFinishLaunchingWithOptions:) (AppDelegate). This registration point guarantees an early, one-time, and deterministic registration at the start of the process, including in the background.
Apple Push Notifcation service
APNSInterface is the protocol for requesting the APNs push token. Unlike the other push provider protocols, this protocol has no token deletion function.
The SDK protocol:
public protocol APNSInterface {
func getToken(completion: @escaping (String?) -> Void)
}
Recommended implementation on the app side
import Altcraft
class APNSProvider: APNSInterface {
func getToken(completion: @escaping (String?) -> Void) {
//your code that returns the APNs token
}
}
Registering the provider in application(_:didFinishLaunchingWithOptions:)
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//other code
AltcraftSDK.shared.push.token.setAPNSTokenProvider(APNSProvider())
}
//other functions
}
Firebase Cloud Messaging
FCMInterface is the protocol for requesting and deleting the Firebase Cloud Messaging push token.
The SDK protocol:
public protocol FCMInterface {
func getToken(completion: @escaping (String?) -> Void)
func deleteToken(completion: @escaping (Bool) -> Void)
}
Recommended implementation on the app side
import FirebaseMessaging
import Altcraft
class FCMProvider: FCMInterface {
/// Retrieves the current FCM token
func getToken(completion: @escaping (String?) -> Void) {
///The APNs token (as data) retrieved from userDefault
let apnsToken = getAPNsTokenDataFromUserDefaults()
///Set the APNs token for FCM before requesting the FCM token
Messaging.messaging().apnsToken = apnsToken
///The FCM token request function
Messaging.messaging().token { token, error in
if error != nil {
completion(nil)
} else {
completion(token)
}
}
}
/// Deletes the current FCM token
func deleteToken(completion: @escaping (Bool) -> Void) {
Messaging.messaging().deleteToken { error in
if error != nil {
completion(false)
} else {
completion(true)
}
}
}
}
Registering the provider in application(_:didFinishLaunchingWithOptions:)
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Other code
AltcraftSDK.shared.push.token.setFCMTokenProvider(FCMProvider())
}
//Other functions
}
Huawei Mobile Services
HMSInterface is the protocol for requesting and deleting the Huawei Mobile Services push token.
The SDK protocol:
public protocol HMSInterface {
func getToken(completion: @escaping (String?) -> Void)
func deleteToken(completion: @escaping (Bool) -> Void)
}
Recommended implementation on the app side
class HMSProvider: HMSInterface {
func getToken(completion: @escaping (String?) -> Void) {
//a variable holding the APNs token (as a string) that is later
//passed to HmsInstanceId.getInstance().getToken(apnsToken)
guard let apnsToken = getAPNsTokenFromUserDefault() else {
completion(nil)
return
}
let token = HmsInstanceId.getInstance().getToken(apnsToken)
completion(token)
}
func deleteToken(completion: @escaping (Bool) -> Void) {
HmsInstanceId.getInstance().deleteToken()
completion(true)
}
}
Registering the provider in application(_:didFinishLaunchingWithOptions:)
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Other code
AltcraftSDK.shared.push.token.setHMSTokenProvider(HMSProvider())
}
//Other functions
}
Preparing the Notification Service Extension
Adding the NSE extension is needed to support Rich Push and guaranteed delivery event registration.
Create a Notification Service Extension app extension:
- Do File — New — Target — Notification Service Extension;
- Choose a name (Product Name) for the extension target;
- Activate it.
Do the following in the Notification Service Extension:
- General:
- Set Minimum Deployments — this is an Xcode build parameter that determines the minimum OS version on which the Notification Service Extension will run;
- Add the Altcraft library in the Frameworks, Libraries and Embedded Content section.
- Signing & Capabilities:
- Specify the
AppGroupidentifier.
Then configure the SDK in UNNotificationServiceExtension:
- Import the
Altcraftlibrary; - In the
didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void)function: - Pass the
appGroupidentifier to theAltcraftNSE.shared.setAppGroup(groupName: appGroupsName)function. This identifier must match the identifier passed to the SDK in the app target; - Set the JWT provider (if JWT authentication of requests is used):
AltcraftNSE.shared.setJWTProvider(provider: jwtProvider); - Check the notification source with the
AltcraftNSE.shared.isAltcraftPush(request)function; - After checking the notification source, pass the
UNNotificationRequestandcontentHandlerto theAltcraftNSE.shared.handleNotificationRequest(request: request, contentHandler: contentHandler)function if the notification source is Altcraft.
The example implementation of UNNotificationServiceExtension below can be used as a ready-made class. Replace all the automatically generated code with this implementation if you do not need additional logic. If you already use a UNNotificationServiceExtension, integrate the Altcraft functions into your implementation.
Example implementation of UNNotificationServiceExtension
import Altcraft
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
/// - important! Specify the App Group identifier.
let appGroupID = "group.your.id"
/// - important! Set the JWT provider if JWT authentication is used.
let jwtProvider = JWTProvider()
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
AltcraftNSE.shared.setAppGroup(groupName: appGroupID)
AltcraftNSE.shared.setJWTProvider(provider: jwtProvider)
if AltcraftNSE.shared.isAltcraftPush(request) {
AltcraftNSE.shared.handleNotificationRequest(request: request, contentHandler: contentHandler)
} else {
contentHandler(request.content)
}
}
override func serviceExtensionTimeWillExpire() {
AltcraftNSE.shared.serviceExtensionTimeWillExpire()
}
}
The app target and the Notification Service Extension target run in separate processes and do not share common objects. Therefore, for the SDK to handle notifications correctly, the JWTProvider and the App Group identifier must also be registered in the NSE extension — for example, in the didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) method. This gives the SDK access to the authorization token when sending the push delivery event ("delivery_event").
Initializing the SDK
Initialization parameters
Configuration parameters are passed with the AltcraftConfiguration class:
public final class AltcraftConfiguration {
private let apiUrl: String
private let rToken: String?
private let appInfo: AppInfo?
private let enableLogging: Bool?
}
The settings of individual modules are passed as separate configurations and added to the Builder with the pushConfig() and inAppConfig() calls.
The configuration file is assembled after calling the build() function of the AltcraftConfiguration.Builder() class: AltcraftConfiguration.Builder().build().
Parameter description:
apiUrl String
Required: Yes
Description: The URL of the Altcraft API endpoint
rToken String?
Default: nil
Required: No
Description: The Altcraft resource token (identifies the resource, database, or account). Used if the only matching type is the device push token issued by the provider.
appInfo AppInfo?
Default: nil
Required: No
Description: The basic app metadata for Firebase Analytics. To set this parameter, use the public SDK struct AppInfo:
public struct AppInfo: Codable {
/// Firebase app_id
public var appID: String
/// Firebase app_instance_id
public var appIID: String
/// Firebase app_version
public var appVer: String
}
enableLogging Bool?
Default: nil
Required: No
Description: Enables/disables logging. Logs can be found by the [Altcraft SDK] prefix.
Push module configuration
The push module settings are passed to the Builder with the pushConfig() call:
func pushConfig(
providerPriority: [String] = [],
pushReceiverModules: [String] = [],
pushChannelName: String? = nil,
pushChannelDescription: String? = nil
) -> Self
providerPriority [String]
Default: an empty list
Required: No
Description: The provider priority list. It is used to automatically update the subscription push token if the token of a higher-priority provider is unavailable. The priority is determined by the index in the list: the item with index 0 is the most prioritized.
Usage example:
providerPriority = [
PushConstants.ProviderName.apns,
PushConstants.ProviderName.firebase,
PushConstants.ProviderName.huawei
]
With this setup:
- the SDK first requests the APNs token; if APNs is unavailable — FCM; if FCM is unavailable — HMS;
- this works provided the corresponding provider protocols are implemented in the app.
For convenience, the SDK contains public constants for setting the parameter value. All constants are in the PushConstants enum:
public enum PushConstants {
public enum ProviderName {
/// Provider name for Firebase
public static let firebase = "ios-firebase"
/// Provider name for APNs
public static let apns = "ios-apns"
/// Provider name for HMS
public static let huawei = "ios-huawei"
}
}
If the value is not set, the provider priority is:
apns —> firebase —> huawei
The parameter value may contain a single item — in this case, only one provider will be used, regardless of the availability of the push token.
You can omit the parameter if:
- only one provider is used in the project;
- the default priority meets the requirements.
pushReceiverModules [String]
Default: an empty list
Required: No
Description: The list of module prefixes containing the AltcraftPushReceiver class implementations.
If specified, the SDK will detect these classes and pass the incoming notification to them. If the classes are not found in any module, the notification will be displayed by the SDK.
pushChannelName String?
Default: nil
Required: No
Description: The push notification channel name
pushChannelDescription String?
Default: nil
Required: No
Description: The push notification channel description
If the parameters are not set, the SDK uses the default values:
- providerPriority —
apns —> firebase —> huawei(if the list is empty); - pushReceiverModules — an empty list;
- pushChannelName and pushChannelDescription — the SDK default values (if
nil).
In-App module configuration
The In-App module settings are passed to the Builder with the inAppConfig() call:
func inAppConfig(
inAppAutoRequest: Bool = true,
placementsRequestInterval: Measurement<UnitDuration>
= Measurement(
value: Double(InAppConstants.PlacementsConfig.requestIntervalSeconds),
unit: .seconds
),
maxPlacements: Int = InAppConstants.PlacementsConfig.maxPlacements
) -> Self
inAppAutoRequest Bool
Default: true
Required: No
Description: When true, the SDK automatically requests In-App placements at initialization and then at the placementsRequestInterval interval. When false, requests are made only on an explicit getPlacements() call or when triggers fire.
placementsRequestInterval Measurement<UnitDuration>
Default: 30 minutes
Required: No
Description: The interval of automatic In-App placement requests. Before an automatic request, the SDK checks the time of the last successful request: if the configured interval has not elapsed yet, the automatic request is not performed. A manual request via getPlacements() is not limited by the interval. The value must be greater than zero, otherwise the SDK records the requestIntervalInvalid error.
maxPlacements Int
Default: 500
Required: No
Description: The maximum number of In-App placements stored in the local storage of the device. If the server response contains more placements than the configured limit, the newest placements (with the largest companyId) are kept in the local storage and the rest are discarded; in this case, the SDK emits the placementsLimitExceeded event and a log entry with the number of received placements (received), the configured limit (limit), and the companyId values of the discarded placements (rejectedCompanyIds). The value must be at least 1, otherwise the SDK records the maxPlacementsInvalid error. When the limit changes in a new configuration, the SDK resets the placement rules checksum (placementRulesChecksum) and requests the actual set of placements without waiting for placementsRequestInterval to elapse; the initial configuration setup does not count as a limit change.
Performing the initialization
Call AltcraftSDK.shared.initialization() whenever you need, but only after setting the AppGroup identifier and the providers implementing the SDK protocols. Requests should be made after the configuration is set.
The following function is used to initialize the SDK:
public func initialization(
configuration: AltcraftConfiguration?,
completion: (@Sendable (Bool) -> Void)? = nil
)
The function expects a AltcraftConfiguration configuration file with the required values set and assembled:
let config = AltcraftConfiguration.Builder(
apiUrl: "your apiUrl",
rToken: "your rToken"
)
.setAppInfo(AppInfo(appID: "your appID", appIID: "your appIID", appVer: "your appVer"))
.pushConfig(
providerPriority: [PushConstants.ProviderName.firebase]
)
.build()
Example of the correct SDK initialization order in AppDelegate.application(_:didFinishLaunchingWithOptions:) (after setting the AppGroup identifier and registering the providers)
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Firebase configuration initialization (if used)
let appGroup = "group.your.id"
AltcraftSDK.shared.setAppGroup(groupName: appGroup)
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
AltcraftSDK.shared.setJWTProvider(provider: JWTProvider())
AltcraftSDK.shared.push.token.setAPNSTokenProvider(APNSProvider())
AltcraftSDK.shared.push.token.setFCMTokenProvider(FCMProvider())
AltcraftSDK.shared.push.token.setHMSTokenProvider(HMSProvider())
AltcraftSDK.shared.push.notificationManager.registerForPushNotifications(for: application)
let config = AltcraftConfiguration.Builder(
apiUrl: "your apiUrl",
rToken: "your rToken"
)
.setAppInfo(AppInfo(appID: "your appID", appIID: "your appIID", appVer: "your appVer"))
.pushConfig(
providerPriority: [PushConstants.ProviderName.firebase]
)
.inAppConfig(
inAppAutoRequest: true
)
.build()
AltcraftSDK.shared.initialization(configuration: config) { success in
if success {
// Actions after successful initialization
}
}
// Other functions
return true
}
// Other functions AppDelegate
}
Example of a minimal working setup
let config = AltcraftConfiguration.Builder(
apiUrl: "https://pxl-example.altcraft.com"
).build()
AltcraftSDK.shared.initialization(configuration: config)