Files
ipx/ios/Sources/App.swift
Ray Slakinski bd535851b0 The iOS app's icon count, pushed by ipx (#433)
Background refresh runs when iOS chooses, and for this app that has
been rarely, so the icon's unread count lagged until the app was
opened (#421). Now ipx sends the count:
- ipx: a push_devices table and POST/DELETE /api/push/devices. With
  [push] set to an APNs key, a task compares each phone's All
  Subscriptions unread count with the last sent, once a minute, and
  sends a changed one to Apple as a badge-only push: no alert, no
  sound. A token Apple says is gone is forgotten. The signing token is
  ES256 over the team and time, made again every forty minutes, inside
  Apple's limits.
- iOS: the aps-environment entitlement. The app registers for push
  once badge permission is given, each launch, and hands its token to
  the page.
- Page: registers the token with ipx as the person signed in, since
  the player's address (ipxapi) lets through only the player's paths,
  and removes it on signing out.

jsonwebtoken here is built without PEM support, so the .p8 key's
base64 is decoded in push.rs rather than turning on a feature and
changing Cargo.lock for one file.

cargo test -p ipx on macOS: 182 pass; the 3 that fail read /proc and
fail there without this change too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:47:28 -05:00

62 lines
2.4 KiB
Swift

import UIKit
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
Badge.registerBackgroundRefresh()
Offline.shared.resume()
Outbox.watch()
return true
}
/// A download kept going while the app was closed has finished (#425): the system launched us
/// to hand it over, and waits on this handler.
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
Offline.shared.backgroundCompletion = completionHandler
Offline.shared.resume()
}
/// Apple's address for this phone's copy of the app (#433), handed to the page to register.
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Push.token = deviceToken.map { String(format: "%02x", $0) }.joined()
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
NSLog("ipx: push registration: %@", error.localizedDescription)
}
}
/// The one window. iOS will not run an app that has not adopted scenes, and a CarPlay app would
/// be a second scene beside this one.
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options: UIScene.ConnectionOptions) {
guard let scene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: scene)
window.rootViewController = WebViewController()
window.makeKeyAndVisible()
self.window = window
}
/// iOS wants the request made as the app leaves, with the user's last look at the list; the
/// one made on coming forward stays for a run that was cut short.
func sceneDidEnterBackground(_ scene: UIScene) {
Badge.scheduleBackgroundRefresh()
}
func sceneDidBecomeActive(_ scene: UIScene) {
Badge.requestPermission()
Badge.scheduleBackgroundRefresh()
Task {
await Outbox.flush()
await Badge.refresh()
}
}
}