iPhone and iPad

There is no Firebase step for iOS. We hold the connection to Apple ourselves, signing each request with the .p8 key from your developer account. On the phone, the person taps Allow, Apple gives the app a device token, and the app passes that token to us in one request.

The SDK

Swift 1.6.0 passes its tests, and bolrach-push goes up on a public Swift Package Manager repository once the publishing account exists.

Until the package is out, the app makes the same calls the SDK makes, and the steps below show them.

1. Upload your APNs key

  1. In your Apple Developer account, under Keys, create a key with Apple Push Notifications service turned on and download the .p8 file. Note the Key ID. The Team ID is under Membership.
  2. In the Bolrach Push console, open the app. Under iOS (APNs), enter the Team ID, the Key ID and the bundle id, for example com.example.shop, then choose the .p8 file.
  3. Turn on the Push Notifications capability in Xcode. A development app in our console talks to Apple's sandbox, and a production app talks to the live service, so make one of each if you ship both kinds of build.

2. Get the token and register

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
        guard granted else { return }
        DispatchQueue.main.async { application.registerForRemoteNotifications() }
    }
    return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let hexToken = deviceToken.map { String(format: "%02x", $0) }.joined()
    Task { try await register(hexToken) }
}
let body: [String: Any] = [
    "app_id": appID,
    "device_id": StableDeviceID.value,          // made once per install and kept
    "anonymous_id": StableAnonymousID.value,    // the same, until the person signs in
    "platform": "ios",
    "push_provider": "apns",
    "token": hexToken,
    "environment": "production",                // a development app talks to Apple's sandbox
    "permission_state": "granted",
]
let installation = try await postJSON("https://api.bolrach.com/push/v1/installations", body)
// Keep both. The receipt token signs this device's later calls; it is not a key.
Keychain.save(installation["id"], for: "bolrach.installation")
Keychain.save(installation["receipt_token"], for: "bolrach.receipt")

Apple calls didRegisterForRemoteNotificationsWithDeviceToken again when the token changes. Register it with the same device_id and the installation keeps its id. With the SDK:

try BolrachPush.shared.initialize("YOUR_APP_ID")
if try await BolrachPush.shared.requestPermission() {
    await UIApplication.shared.registerForRemoteNotifications()
}
// in didRegisterForRemoteNotificationsWithDeviceToken:
try await BolrachPush.shared.register(deviceToken: deviceToken)
// after sign-in, and on sign-out:
try await BolrachPush.shared.login("user_8123")
try await BolrachPush.shared.logout()

3. Confirm delivery

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
    let info = response.notification.request.content.userInfo
    guard let delivery = info["delivery_id"] as? String else { return }
    var request = URLRequest(url: URL(string: "https://api.bolrach.com/push/v1/receipts")!)
    request.httpMethod = "POST"
    request.setValue("Bearer \(receiptToken)", forHTTPHeaderField: "Authorization")
    request.setValue(appID, forHTTPHeaderField: "X-Bolrach-App-ID")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONSerialization.data(withJSONObject:
        ["installation_id": installationID, "delivery_id": delivery, "event": "opened"])
    _ = try? await URLSession.shared.data(for: request)
}

With the SDK this is try await BolrachPush.shared.report(deliveryID: delivery, event: "opened").

4. Send

The same POST /messages reaches iOS. Set apns.thread_id to group a conversation in Notification Center. To show an image, set image_url and add a Notification Service Extension that downloads it.

{ "app_id": "YOUR_APP_ID", "target": { "type": "user", "id": "user_8123" },
  "title": "Your order is out for delivery", "body": "Arriving between 2 and 4 pm.",
  "image_url": "https://cdn.example.com/orders/8123/map.png",
  "apns": { "thread_id": "order-8123", "interruption_level": "time-sensitive" } }

Machine-readable: openapi.json. Console: bolrach.com/push.