SDK Configuration
Prerequisites
- Push provider SDKs are integrated into the app project (see the push provider integration guides).
- The Altcraft package is added via Swift Package Manager.
App preparation
Target settings
Configure the following app target settings:
-
General:
- Ensure the Altcraft library is added under Frameworks, Libraries, and Embedded Content of the app target.
-
Signing & Capabilities:
- PushNotifications;
- AppGroups — specify a group identifier (adding an App Group identifier is required for data exchange with the Notification Service Extension);
- Background Modes — enable Background fetch, Background processing.
-
Info:
- Add the key Permitted background task scheduler identifiers with the value "lib.Altcraft.bgTask.systemControl". This registers the
bgTaskthat retries failed server requests in background mode. Optional — when using Firebase Cloud Messaging, add the keyFirebaseAppDelegateProxyEnabled (Boolean)with the valueNO. This disables the default method swizzling ofAppDelegateperformed by Firebase Messaging.
- Add the key Permitted background task scheduler identifiers with the value "lib.Altcraft.bgTask.systemControl". This registers the
AppDelegate settings
In AppDelegate.application(_:didFinishLaunchingWithOptions:), do the following:
- pass the
AppGroupidentifier to the SDK - register the SDK
BGTask(background tasks) - (optional) initialize
UNUserNotificationCenterfeatures on the SDK side
Setting the AppGroup identifier
Adding an AppGroup identifier is required to exchange data with the Notification Service Extension and for correct SDK operation. Use setAppGroup():
// Configure the App Group and initialize Core Data for the shared container
AltcraftSDK.shared.setAppGroup(groupName: String)
Registering the BGTask
Registering SDK background tasks enables retrying failed requests in the background. Use registerBackgroundTask():
// Register the SDK background task and schedule periodic execution (about every 3 hours)
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
Initializing UNUserNotificationCenter features (optional)
The SDK includes the NotificationManager class, which:
- configures
UNUserNotificationCenterand requests notification permissions (alert/sound/badge); - calls
UIApplication.registerForRemoteNotifications()and ensures correct APNs registration; - displays notifications in foreground;
- handles notification taps and action buttons — navigates to a specified URL or opens the app;
- registers the push open event ("open").
To use the SDK NotificationManager, call this function in AppDelegate.application(_:didFinishLaunchingWithOptions:):
func registerForPushNotifications(for application: UIApplication, completion: ((_ granted: Bool, _ error: Error?) -> Void)? = nil): Void
This function sets the delegate, requests the user’s permission, and registers the app for push notifications on the SDK side.
Example of a correct AppDelegate.application setup
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let appGroup = "group.your.name.example"
// Set App Group ID
AltcraftSDK.shared.setAppGroup(groupName: appGroup)
// Register the SDK background task
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
// Initialize SDK UNUserNotificationCenter features
AltcraftSDK.shared.notificationManager.registerForPushNotifications(for: application)
// Other functions
return true
}
// Other AppDelegate functions
}
Using this class is optional; you can replace it with your own implementation. To do so:
- set the notification center delegate:
UNUserNotificationCenter.current().delegate = self; - request notification permissions and call
UIApplication.shared.registerForRemoteNotifications()strictly on the main thread; - implement
userNotificationCenter(_:willPresent:withCompletionHandler:)to display notifications in foreground (banner/alert/sound/badge); - call
AltcraftSDK.shared.pushEventFunctions.openEvent(from:)inuserNotificationCenter(_:didReceive:withCompletionHandler:)when handling a notification tap (registering the "open" event); - implement custom logic for handling notification taps (URL/Deep Link/buttons), and ensure a fallback scenario (default navigation if no valid link is present).
JWT protocol setup (optional)
JWTInterface is the protocol for requesting a JWT token. It provides a current JWT token from the app upon SDK request. Implement this protocol if API requests use JWT authentication. JWT confirms that user identifiers are authenticated by the app.
JWT authentication is required if the matching type differs from push data from the subscription (for example, when the user identifier is an email or phone number).
SDK protocol:
public protocol JWTInterface {
func getToken() -> String?
}
App-side implementation
import Altcraft
class JWTProvider: JWTInterface {
func getToken() -> String? {
// your code that returns a 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.setJWTProvider(provider: JWTProvider())
}
// Other functions
}
getJWT() is a synchronous function. The SDK execution thread will pause until JWT is obtained. It’s recommended that getJWT() returns immediately from cache (in-memory, SharedPreferences or EncryptedSharedPreferences) to speed up requests. Ideally, prepare a fresh JWT as early as possible (at app start) and store it in cache so the token is available without delays. Returning nil is acceptable if no value is available.
Provider push token request and deletion protocols
These token request/deletion protocol implementations ensure the current token is used and allow dynamic provider switching at the client’s request. Implement only the protocols for the push providers used in your project.
Register providers in application(_:didFinishLaunchingWithOptions:) (AppDelegate). This registration point guarantees early, single, and deterministic registration at process start, including in background.
Apple Push Notification service
APNSInterface — the protocol for requesting an APNs push token. This protocol has no token deletion function, unlike other push provider protocols.
SDK protocol:
public protocol APNSInterface {
func getToken(completion: @escaping (String?) -> Void)
}
Recommended app-side implementation
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.pushTokenFunction.setAPNSTokenProvider(APNSProvider())
}
// other functions
}
Firebase Cloud Messaging
FCMInterface — the protocol for requesting and deleting a Firebase Cloud Messaging push token.
SDK protocol:
public protocol FCMInterface {
func getToken(completion: @escaping (String?) -> Void)
func deleteToken(completion: @escaping (Bool) -> Void)
}
Recommended app-side implementation
import FirebaseMessaging
import Altcraft
class FCMProvider: FCMInterface {
/// Retrieves the current FCM token
func getToken(completion: @escaping (String?) -> Void) {
/// APNs token (as Data), retrieved from UserDefaults
let apnsToken = getAPNsTokenDataFromUserDefaults()
/// Set the APNs token for FCM before requesting the FCM token
Messaging.messaging().apnsToken = apnsToken
/// Request the FCM token
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.pushTokenFunction.setFCMTokenProvider(FCMProvider())
}
// Other functions
}
Huawei Mobile Services
HMSInterface — the protocol for requesting and deleting a Huawei Mobile Services push token.
SDK protocol:
public protocol HMSInterface {
func getToken(completion: @escaping (String?) -> Void)
func deleteToken(completion: @escaping (Bool) -> Void)
}
Recommended app-side implementation
class HMSProvider: HMSInterface {
func getToken(completion: @escaping (String?) -> Void) {
// variable containing the APNs token (as a String), which 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.pushTokenFunction.setHMSTokenProvider(HMSProvider())
}
// Other functions
}
Preparing the Notification Service Extension
Adding an NSE is required for Rich Push support and guaranteed delivery event registration.
Create a Notification Service Extension:
- **File** — **New** — **Target** — **Notification Service Extension**;
- Choose a Product Name for the extension target;
- Activate it.
In the Notification Service Extension:
-
General:
- Set Minimum Deployments — the Xcode build setting that defines the minimum OS version supported by the Notification Service Extension;
- Add the Altcraft library under Frameworks, Libraries and Embedded Content.
-
Signing & Capabilities:
- AppGroups — specify the
AppGroupidentifier.
- AppGroups — specify the
Then configure the SDK in UNNotificationServiceExtension:
-
Import the
Altcraftlibrary; -
Create an instance of
AltcraftPushReceiver(); -
In
didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void):- Pass the
appGroupidentifier toAltcraftSDK.shared.setAppGroup(groupName: appGroupsName). This identifier must match the one passed to the SDK in the app target; - Set the JWT provider (if JWT is used for request authentication):
AltcraftSDK.shared.setJWTProvider(provider: jwtProvider); - Validate the notification source using
isAltcraftPush(request)ofAltcraftPushReceiver():service.isAltcraftPush(request); - After validation, pass the
UNNotificationRequestandcontentHandlertoAltcraftPushReceiver().didReceive:self.service.didReceive(request, withContentHandler: contentHandler)if the notification source is Altcraft.
- Pass the
The example below can be used as a ready-to-use UNNotificationServiceExtension class. Replace all autogenerated code with this implementation if no extra logic is required. If you already use UNNotificationServiceExtension, integrate the Altcraft functions into your implementation.
Example implementation of UNNotificationServiceExtension
import Altcraft
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var service = AltcraftPushReceiver()
/// - Important! Set your appGroups name
var appGroupsName = "your.app.group"
let jwtProvider = JWTProvider()
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
AltcraftSDK.shared.setAppGroup(groupName: appGroupsName)
AltcraftSDK.shared.setJWTProvider(provider: jwtProvider)
if service.isAltcraftPush(request) {
self.service.didReceive(request, withContentHandler: contentHandler)
} else {
contentHandler(request.content)
}
}
override func serviceExtensionTimeWillExpire() {service.serviceExtensionTimeWillExpire()}
}
The app target and the Notification Service Extension target run in separate processes and do not share common objects. Therefore, to process notifications correctly, the SDK must also register the JWTProvider in the NSE — for example, inside
didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void). This ensures the SDK has access to the authorization token when sending the push delivery event ("delivery_event").
SDK initialization
Initialization parameters
Use the AltcraftConfiguration class to pass configuration parameters:
public final class AltcraftConfiguration {
private let apiUrl: String
private let rToken: String?
private var appInfo: AppInfo?
private var providerPriorityList: [String]?
}
The configuration object is built after calling the build() function of AltcraftConfiguration.Builder(): AltcraftConfiguration.Builder().build().
Parameter descriptions:
apiUrl String
Required: Yes
Description: Altcraft API endpoint URL
rToken String?
Default: nil
Required: No
Description: Altcraft role token (identifies a resource, DB, or account). Used when the only matching type is the device’s push token issued by a provider.
appInfo AppInfo?
Default: nil
Required: No
Description: Basic app metadata for Firebase Analytics. To set this parameter, use the public SDK struct AppInfo:
public struct AppInfo: Codable {
/// Unique App ID (Firebase `app_id`)
public var appID: String
/// Unique app instance ID (Firebase `app_instance_id`)
public var appIID: String
/// App version (Firebase `app_version`)
public var appVer: String
}
providerPriorityList [String]?
Default: nil
Required: No
Description: Provider priority list. Used to automatically refresh the subscription’s push token if the higher-priority provider token is unavailable. Priority is defined by the index in the list: the element at index 0 has the highest priority.
Usage example:
providerPriorityList = [
Constants.ProviderName.apns,
Constants.ProviderName.firebase,
Constants.ProviderName.huawei
]
With this configuration:
- The SDK first requests the APNs token; if APNs is unavailable — FCM; if FCM is unavailable — HMS;
- Works provided the corresponding provider protocols are implemented in the app.
For convenient assignment, the SDK exposes public constants in enum Constants:
public enum Constants {
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 not specified, the default provider priority is:
apns → firebase → huawei
The parameter value may contain a single element — in this case, only that provider will be used regardless of push token availability.
You can omit this parameter if:
- only one provider is used in the project;
- the default priority fits your needs.
Performing initialization
Call AltcraftSDK.shared.initialization() when needed, but only after setting the AppGroup identifier and registering the providers that implement the SDK protocols. Make requests after the configuration is set.
Use public func initialization(configuration: AltcraftConfiguration?, completion: ((Bool) -> Void)? = nil) to initialize the SDK:
// SDK initialization and configuration
AltcraftSDK.shared.initialization(configuration: AltcraftConfiguration?, completion: ((Bool) -> Void)? = nil)
The initialization(configuration: AltcraftConfiguration?, completion: ((Bool) -> Void)? = nil) function expects an AltcraftConfiguration object with required values set and built:
let config = AltcraftConfiguration.Builder()
.setApiUrl("your apiUrl")
.setRToken("your rToken")
.setAppInfo(AppInfo(appID: "your appID", appIID: "your appIID", appVer: "your appVer"))
.setProviderPriorityList([Constants.ProviderName.firebase])
.build()
Example of the correct initialization order in AppDelegate.application(_:didFinishLaunchingWithOptions:) (after setting the AppGroup ID and registering providers)
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Firebase configuration can be placed here if Firebase is used in the project
let appGroup = "group.altcraft.apns.example"
AltcraftSDK.shared.setAppGroup(groupName: appGroup)
AltcraftSDK.shared.backgroundTasks.registerBackgroundTask()
AltcraftSDK.shared.setJWTProvider(provider: JWTProvider())
AltcraftSDK.shared.pushTokenFunction.setAPNSTokenProvider(APNSProvider())
AltcraftSDK.shared.pushTokenFunction.setFCMTokenProvider(FCMProvider())
AltcraftSDK.shared.pushTokenFunction.setHMSTokenProvider(HMSProvider())
AltcraftSDK.shared.notificationManager.registerForPushNotifications(for: application)
let config = AltcraftConfiguration.Builder()
.setApiUrl("your apiUrl")
.setRToken("your rToken")
.setAppInfo(AppInfo(appID: "your appID", appIID: "your appIID", appVer: "your appVer"))
.setProviderPriorityList([Constants.ProviderName.firebase])
.build()
AltcraftSDK.shared.initialization(configuration: config) { success in
if success {
// actions after successful initialization
}
}
// Other functions
return true
}
// Other AppDelegate functions
}