docs: add plugin feedback doc for iOS scheduleDualNotification

Add plugin-feedback-ios-scheduleDualNotification.md for the
daily-notification-plugin repo: config shape from the app, expected
behavior, and acceptance criteria so iOS can implement or fix
scheduleDualNotification (currently returns UNIMPLEMENTED).
This commit is contained in:
Jose Olarte III
2026-03-17 21:05:06 +08:00
parent e155e55e49
commit 3c262c9eeb

View File

@@ -0,0 +1,115 @@
# Plugin Feedback: Implement scheduleDualNotification on iOS
**Target repo:** daily-notification-plugin (iOS native layer)
**Purpose:** Document for implementing or fixing `scheduleDualNotification` on iOS so the consuming app (TimeSafari / crowd-funder) can enable “New Activity” notifications.
**Consuming app doc:** `doc/notification-new-activity-lay-of-the-land.md`
---
## Current behavior
- The **consuming app** calls `DailyNotification.scheduleDualNotification({ config })` from TypeScript when the user turns on “New Activity Notification” and picks a time (native iOS).
- On **iOS**, the plugin rejects with **`code: "UNIMPLEMENTED"`** (observed in Xcode: `[AccountViewView] scheduleNewActivityDualNotification failed: {"code":"UNIMPLEMENTED"}`).
- On **Android**, the same call is expected to work (dual schedule: content fetch + user notification).
The app has already:
- Called `configureNativeFetcher({ apiBaseUrl, activeDid, jwtToken })` so the plugin can use the native fetcher for API-driven content.
- Called `updateStarredPlans({ planIds })` so the fetcher knows which plans to query.
- Built a `config` object that matches the plugins `DualScheduleConfiguration` (see below).
So the missing piece on iOS is a **working implementation** of `scheduleDualNotification` that accepts this config and schedules the dual flow (content fetch at one time, user notification at a later time).
---
## Call from the consuming app
```ts
await DailyNotification.scheduleDualNotification({ config });
```
`config` is built by the apps `buildDualScheduleConfig({ notifyTime })` and has the following shape.
---
## Config shape the app sends
The app sends a single `config` object that matches the plugins `DualScheduleConfiguration` (see `definitions.ts`). Example for `notifyTime: "18:30"` (6:30 PM):
```json
{
"contentFetch": {
"enabled": true,
"schedule": "25 18 * * *",
"callbacks": {}
},
"userNotification": {
"enabled": true,
"schedule": "30 18 * * *",
"title": "New Activity",
"body": "Check your starred projects and offers for updates.",
"sound": true,
"priority": "normal"
},
"relationship": {
"autoLink": true,
"contentTimeout": 300000,
"fallbackBehavior": "show_default"
}
}
```
- **Cron format:** `"minute hour * * *"` (daily at that local time).
- **contentFetch.schedule:** 5 minutes **before** the users chosen time (e.g. 18:25 for notify at 18:30).
- **userNotification.schedule:** The users chosen time (e.g. 18:30).
- **contentFetch.callbacks:** The app sends `{}`; the actual fetch is done by the **native fetcher** (already configured via `configureNativeFetcher`). The plugin should run the content-fetch job at the contentFetch cron and use the native fetcher to get content; at userNotification time it should show a notification using that content or the fallback title/body.
- **relationship.contentTimeout:** Milliseconds to wait for content before showing the notification (app uses 5 minutes = 300000).
- **relationship.fallbackBehavior:** `"show_default"` means if content isnt ready in time, show the notification with the default title/body from `userNotification`.
The app does **not** send `contentFetch.url` or `contentFetch.timesafariConfig`; it relies on the native fetcher and `configureNativeFetcher` / `updateStarredPlans` for API behavior.
---
## Expected plugin behavior (iOS)
1. **Accept** the `config` argument (object with `contentFetch`, `userNotification`, and optional `relationship`).
2. **Parse** the cron expressions for `contentFetch.schedule` and `userNotification.schedule` (e.g. using a shared cron parser or the same approach as Android).
3. **Schedule** two things:
- **Content fetch:** At the time given by `contentFetch.schedule`, run the **native notification content fetcher** (the one configured via `configureNativeFetcher`). Store the result in the plugins cache (or equivalent) for use when the user notification fires.
- **User notification:** At the time given by `userNotification.schedule`, show a local notification. Use cached content from the fetch if available and within `relationship.contentTimeout`; otherwise use `userNotification.title` and `userNotification.body` (per `relationship.fallbackBehavior: "show_default"`).
4. **Do not** reject with `UNIMPLEMENTED`; resolve the promise once scheduling has succeeded (or reject with a descriptive error if scheduling fails).
5. **cancelDualSchedule()** should cancel both the content-fetch schedule and the user-notification schedule so the user can turn off New Activity from the app.
Alignment with **Android** (if implemented there) is desirable: same config shape, same semantics (prefetch then notify, fallback to default title/body). The plugins **definitions.ts** already defines `DualScheduleConfiguration`, `ContentFetchConfig`, `UserNotificationConfig`, and the `scheduleDualNotification` / `cancelDualSchedule` API.
---
## Where to look in the plugin (iOS)
- **Plugin entry:** `ios/Plugin/DailyNotificationPlugin.swift` (or equivalent)—find the handler for `scheduleDualNotification` (e.g. method that receives `call.getObject("config")`).
- **Android reference:** `android/` implementation of `scheduleDualNotification` and how it schedules WorkManager/alarms for content fetch and for the user notification.
- **Definitions:** `src/definitions.ts``DualScheduleConfiguration`, `scheduleDualNotification`, `cancelDualSchedule`.
- **Native fetcher:** The app configures the native fetcher before calling `scheduleDualNotification`; the iOS plugin should invoke that same fetcher when the content-fetch job runs (BGAppRefreshTask or equivalent), not a URL from the config.
---
## Acceptance criteria
- [ ] On iOS, calling `DailyNotification.scheduleDualNotification({ config })` with the config shape above **does not** reject with `code: "UNIMPLEMENTED"`.
- [ ] The content-fetch job is scheduled at `contentFetch.schedule` and uses the configured native fetcher to fetch content.
- [ ] The user notification is scheduled at `userNotification.schedule` and shows with API-derived content when available, or with `userNotification.title` / `userNotification.body` as fallback.
- [ ] Calling `DailyNotification.cancelDualSchedule()` cancels both schedules on iOS.
- [ ] Behavior is consistent with Android where applicable (same config, same lifecycle).
---
## Relationship to consuming app
The consuming app will continue to call:
1. `configureNativeFetcher(...)` on startup and when enabling New Activity.
2. `updateStarredPlans({ planIds })` when enabling or when Account view loads with New Activity on.
3. `scheduleDualNotification({ config })` when the user turns on New Activity and picks a time.
4. `cancelDualSchedule()` when the user turns off New Activity.
No change to the apps config shape or call order is planned; the fix is entirely on the plugin iOS side to implement or correct `scheduleDualNotification` (and ensure `cancelDualSchedule` clears the dual schedule).