Add TimeSafariNativeFetcher (plansLastUpdatedBetween parity with Android) and call DailyNotificationPlugin.registerNativeFetcher from AppDelegate before JS configureNativeFetcher; broaden DailyNotificationDelivered scheduled_time types in willPresent. Wire the new file into the App target; normalize PBX object IDs to 24-char hex. Document plugin ≥3 handoff (consuming-app-handoff-ios-native-fetcher-chained-dual), refresh iOS/Android parity and notification-from-api-call file tables.
81 lines
4.5 KiB
Markdown
81 lines
4.5 KiB
Markdown
# Consuming app handoff: iOS native fetcher + chained dual (mirror)
|
||
|
||
**Canonical source:** `daily-notification-plugin` repo, `doc/CONSUMING_APP_HANDOFF_IOS_NATIVE_FETCHER_AND_CHAINED_DUAL.md` (same content as below for offline use).
|
||
|
||
---
|
||
|
||
## Implemented in this app
|
||
|
||
- **`ios/App/App/TimeSafariNativeFetcher.swift`** — Swift `NativeNotificationContentFetcher` mirroring `TimeSafariNativeFetcher.java` (`POST …/plansLastUpdatedBetween`, starred IDs from `daily_notification_timesafari.starredPlanIds`, JWT pool selection, pagination key `daily_notification_timesafari.last_acked_jwt_id`, aggregated copy).
|
||
- **`AppDelegate.swift`** — `DailyNotificationPlugin.registerNativeFetcher(TimeSafariNativeFetcher.shared)` at launch **before** any JS `configureNativeFetcher`; foreground handler reads `scheduled_time` as `Int64`, `NSNumber`, or `Int` for `DailyNotificationDelivered`.
|
||
|
||
## Dependency
|
||
|
||
- **`@timesafari/daily-notification-plugin`** must be **≥ 3.0.0** (register native fetcher, chained dual, iOS `updateStarredPlans`). Declare it in `package.json` from the official remote (`git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git`, branch or tag as needed), then `npm install` so `package-lock.json` resolves the published tree.
|
||
|
||
## Bump / sync (after plugin version is resolved)
|
||
|
||
1. `npm install`
|
||
2. `npx cap sync ios && npx cap sync android`
|
||
3. `cd ios/App && pod install`
|
||
4. Clean build in Xcode / Android Studio
|
||
|
||
## QA focus
|
||
|
||
- iOS: Fetcher registered before `configureNativeFetcher`; `updateStarredPlans` not `UNIMPLEMENTED`.
|
||
- Both: New Activity fires **after** prefetch for that cycle where the plugin implements chaining.
|
||
- Android: Existing `MainActivity.setNativeFetcher` unchanged; regression-test `cancelDualSchedule` vs Daily Reminder.
|
||
|
||
---
|
||
|
||
## Original handoff text (from plugin)
|
||
|
||
This document is for the **host app** repository (e.g. crowd-funder-for-time-pwa) after bumping `@timesafari/daily-notification-plugin` to a version that includes:
|
||
|
||
- **iOS** `NativeNotificationContentFetcher`–style registration (`DailyNotificationPlugin.registerNativeFetcher`)
|
||
- **iOS** `updateStarredPlans` / `getStarredPlans` (parity with Android `daily_notification_timesafari` / `starredPlanIds` semantics)
|
||
- **iOS** chained dual flow: user notification is **armed only after** prefetch completes (delay if fetch is late; max slip 15 minutes before fallback copy)
|
||
- **Android** chained dual flow: exact **notify** alarm is scheduled **after** dual prefetch completes (no longer scheduled at initial `scheduleDualNotification` before fetch)
|
||
|
||
Material from `doc/new-activity-notifications-ios-android-parity.md` still applies; the plugin doc adds **app-side** steps not spelled out there.
|
||
|
||
### 1. iOS — register native fetcher before `configureNativeFetcher`
|
||
|
||
The plugin **rejects** `configureNativeFetcher` if no fetcher is registered (aligned with Android).
|
||
|
||
**In `AppDelegate` (or earliest app startup before Capacitor calls into the plugin):**
|
||
|
||
```swift
|
||
import TimesafariDailyNotificationPlugin
|
||
|
||
DailyNotificationPlugin.registerNativeFetcher(TimeSafariNativeFetcher.shared)
|
||
```
|
||
|
||
Implement **`TimeSafariNativeFetcher`** as a Swift type that:
|
||
|
||
- Conforms to `NativeNotificationContentFetcher`
|
||
- Implements `fetchContent(context: FetchContext) async throws -> [NotificationContent]` with the same **Endorser** behavior as `TimeSafariNativeFetcher.java`
|
||
- Implements `configure(apiBaseUrl:activeDid:jwtToken:jwtTokenPool:)` if the fetcher needs credentials pushed from TypeScript
|
||
|
||
**Starred plan IDs for the fetcher:** Read JSON array string from UserDefaults key **`daily_notification_timesafari.starredPlanIds`** (written by `updateStarredPlans` from JS).
|
||
|
||
### 2. iOS — `UNUserNotificationCenterDelegate` / rollover
|
||
|
||
Chained dual notifications set:
|
||
|
||
- `notification_id` = `org.timesafari.dailynotification.dual`
|
||
- `scheduled_time` = `NSNumber` (fire time in ms)
|
||
|
||
Ensure **`DailyNotificationDelivered`** forwards **`notification_id`** and **`scheduled_time`** from **notification content `userInfo`**.
|
||
|
||
### 3. Android — no API change for `setNativeFetcher`
|
||
|
||
Host apps that already call `DailyNotificationPlugin.setNativeFetcher(TimeSafariNativeFetcher(...))` keep that flow.
|
||
|
||
**Behavior change:** the dual **notify** alarm is scheduled when **dual prefetch work finishes**, not at the initial `scheduleDualNotification` only.
|
||
|
||
### 4. Assumptions
|
||
|
||
- Swift host implements `TimeSafariNativeFetcher`; the plugin does **not** embed `plansLastUpdatedBetween` on iOS when a host fetcher is registered (mirrors Android).
|
||
- Module import: `TimesafariDailyNotificationPlugin` (Pod `TimesafariDailyNotificationPlugin`).
|