Skip to content

Instantly share code, notes, and snippets.

@agileapplications
Last active April 14, 2021 07:54
Show Gist options
  • Save agileapplications/6707b6829d6bdfad41cafd4c9d8f246b to your computer and use it in GitHub Desktop.
Save agileapplications/6707b6829d6bdfad41cafd4c9d8f246b to your computer and use it in GitHub Desktop.
Sample implementation to receive unencrypted ably messages via native Apple Push Notification Service (APNS) delivered by ably in foreground and background.
//
// AppDelegate.swift
// swift-ios
//
import Foundation
import UIKit
import Ably
class AppDelegate: NSObject, UIApplicationDelegate, ARTPushRegistererDelegate, UNUserNotificationCenterDelegate {
var realtime: ARTRealtime!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// assign ably instance
realtime = getAblyRealtime()
UNUserNotificationCenter.current().delegate = self
// probably this is not necessary if only background notifications are used
UNUserNotificationCenter.current().requestAuthorization(options: [.provisional, .badge, .alert]) { granted, _ in
print("Permission granted: \(granted)")
}
// registers device using server authentication to ably with client id
realtime.push.activate()
// starts APNS registration process
UIApplication.shared.registerForRemoteNotifications()
// Do I have to call realtime.push.deactivate()? When, why, how?
return true
}
// MARK: APNS Push Message Delegate
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
print("APNS: ", userInfo)
completionHandler(.newData)
}
// MARK: APNS Setup Delegate
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("registering for remote notifications with device token...")
ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: realtime)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("ERROR: didFailToRegisterForRemoteNotificationsWithError")
ARTPush.didFailToRegisterForRemoteNotificationsWithError(error, realtime: realtime)
}
// MARK: ARTPushRegistererDelegate
func didActivateAblyPush(_ error: ARTErrorInfo?) {
print("SUCCESS: didActivateAblyPush")
if let error = error {
print("ERROR: didActivateAblyPush, err=\(String(describing: error))")
return
}
}
func didDeactivateAblyPush(_ error: ARTErrorInfo?) {
if let error = error {
print("ERROR: didDeactivateAblyPush", error)
return
}
}
// MARK: Ably Realtime Client (Singleton)
func getAblyRealtime() -> ARTRealtime {
if let realtime = realtime {
return realtime
}
realtime = ARTRealtime(options: getOptions())
return realtime
}
func getOptions() -> ARTClientOptions {
let options = ARTClientOptions()
// this is dependent on auth method (we use server side authentication)
// https://ably.com/tutorials/token-authentication
options.authUrl = URL(string: "https://my-server-endpoint/ably_token_requests")!
options.authMethod = "POST"
options.authHeaders = ["X-API-KEY": "secret"]
options.pushRegistererDelegate = self
return options
}
}
@agileapplications
Copy link
Author

agileapplications commented Apr 13, 2021

Ably Push Inspector was able to push messages, but local server implementation (ably-ruby) was not, this worked in the end:

ably_client.push.admin.publish({ client_id: client_id }, { 
  notification: { title: "" }, 
  data: { action: event }, 
  apns: { aps: { "content-available": 1 } }, 
})

Two things I figured out:

  1. If notification hash is not present or just empty, it does not get delivered, at least: notification: { title: "" } has to be present
  2. The part apns: { aps: { "content-available": 1 } } is necessary to also get those messages when app is in the background

@agileapplications
Copy link
Author

agileapplications commented Apr 13, 2021

My App uses the latest SwiftUI skeleton with App and @main. I had to create the separate AppDelegate and reference it:

// Main.swift

import SwiftUI

@main
struct Main: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            MainNavigation()
        }
    }
}

@agileapplications
Copy link
Author

agileapplications commented Apr 13, 2021

Last 2 questions:

  1. When, how, why, where and if I have to call realtime.push.deactivate() https://gist.github.com/agileapplications/6707b6829d6bdfad41cafd4c9d8f246b#file-appdelegate-swift-L29
  2. Is there a way to prevent Echos like we can do on the web with the connectionKey so that I can send sth to the server with a request and the push to APNS will not happen if the sender was already the device?
    -> an idea could be not to send push notification if they came in from specific mobile endpoints, correct? But then I am not able to sync between two mobile phone (edge case, but still not as nice as the connectionKey)

@agileapplications
Copy link
Author

In general what is your recommendation?

App in Background = Use APNS for Message Delivery
App in Foreground = Use Ably Realtime steady connection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment