chore(release): v3.0.0 — iOS native fetcher, starred plans, chained dual (iOS + Android)
BREAKING CHANGE (iOS): configureNativeFetcher now requires DailyNotificationPlugin.registerNativeFetcher(_) first, aligned with Android. iOS: - Add NativeNotificationContentFetcher SPI, registry, FetchContext, timeout helper - Add updateStarredPlans / getStarredPlans; persist daily_notification_timesafari.starredPlanIds - Chained dual: prefetch only on scheduleDualNotification; arm one-shot UN after fetch - configureNativeFetcher invokes fetcher.configure; BG fetch prefers registered fetcher - Public NotificationContent for host implementations Android: - Dual notify alarm scheduled after dual FetchWorker completes (DualScheduleNotifyScheduler) - Persist dual_notify_schedule_id; remove upfront NotifyReceiver for dual setup Docs: CONSUMING_APP_HANDOFF_IOS_NATIVE_FETCHER_AND_CHAINED_DUAL.md; CHANGELOG 3.0.0 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Consuming app handoff: iOS native fetcher + chained dual schedule
|
||||
|
||||
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; this file adds **app-side** steps not spelled out there.
|
||||
|
||||
---
|
||||
|
||||
## 1. iOS — register native fetcher before `configureNativeFetcher`
|
||||
|
||||
The plugin now **rejects** `configureNativeFetcher` if no fetcher is registered (aligned with Android).
|
||||
|
||||
**In `AppDelegate` (or earliest app startup before Capacitor calls into the plugin):**
|
||||
|
||||
```swift
|
||||
import CapacitorDailyNotification // actual product module name may match the Pod (e.g. CapacitorDailyNotification)
|
||||
|
||||
// After: import DailyNotificationPlugin if your target uses a different module name — use the same module that exposes DailyNotificationPlugin.
|
||||
|
||||
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` (`POST …/api/v2/report/plansLastUpdatedBetween`, starred plan IDs, JWT pool selection, aggregation copy, pagination / `last_acked_jwt_id` as in Java)
|
||||
- Implements `configure(apiBaseUrl:activeDid:jwtToken:jwtTokenPool:)` if the fetcher needs credentials pushed from TypeScript (optional; TS still persists `native_fetcher_config` UserDefaults key)
|
||||
|
||||
**Starred plan IDs for the fetcher:** Read JSON array string from UserDefaults key **`daily_notification_timesafari.starredPlanIds`** (written by `updateStarredPlans` from JS). Format matches Android: JSON array of strings.
|
||||
|
||||
---
|
||||
|
||||
## 2. iOS — `UNUserNotificationCenterDelegate` / rollover
|
||||
|
||||
Chained dual notifications set:
|
||||
|
||||
- `notification_id` = `org.timesafari.dailynotification.dual` (same stable identifier as before)
|
||||
- `scheduled_time` = `NSNumber` (fire time in ms)
|
||||
|
||||
Ensure your existing `DailyNotificationDelivered` bridge still forwards **`notification_id`** and **`scheduled_time`** from **notification content `userInfo`** (not only from a custom payload). Foreground presentation handlers should read `notification.request.content.userInfo`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Android — no API change for `setNativeFetcher`
|
||||
|
||||
Host apps that already call `DailyNotificationPlugin.setNativeFetcher(TimeSafariNativeFetcher(...))` and `configureNativeFetcher` from JS keep that flow.
|
||||
|
||||
**Behavior change:** the dual **notify** alarm is no longer scheduled at the initial `scheduleDualNotification` call; it is scheduled when **dual prefetch work finishes** (success or hard failure path), at `max(nextNotifyAt, now)` so late prefetch delays the notification.
|
||||
|
||||
---
|
||||
|
||||
## 4. Bump and sync
|
||||
|
||||
1. Bump **`@timesafari/daily-notification-plugin`** in the app `package.json`.
|
||||
2. `npm install`
|
||||
3. `npx cap sync ios && npx cap sync android`
|
||||
4. iOS: `cd ios/App && pod install` (adjust path if your app uses a different `ios` layout)
|
||||
5. Clean build in Xcode / Android Studio
|
||||
|
||||
---
|
||||
|
||||
## 5. QA focus
|
||||
|
||||
- **iOS:** Register fetcher **before** any `configureNativeFetcher` from the web layer; confirm `updateStarredPlans` is no longer `UNIMPLEMENTED`.
|
||||
- **Both:** New Activity dual path: first notification should appear **after** prefetch for that cycle, not at a fixed time with stale API text.
|
||||
- **Android:** Regression-test `cancelDualSchedule` and Daily Reminder (should remain independent).
|
||||
|
||||
---
|
||||
|
||||
## 6. Assumptions
|
||||
|
||||
- Swift host implements `TimeSafariNativeFetcher`; the plugin does **not** embed `plansLastUpdatedBetween` on iOS when a host fetcher is registered (mirrors Android).
|
||||
- Module import name for the Capacitor iOS plugin follows your Pod (`CapacitorDailyNotification` in `CapacitorDailyNotification.podspec`).
|
||||
Reference in New Issue
Block a user