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:
115
doc/plugin-feedback-ios-scheduleDualNotification.md
Normal file
115
doc/plugin-feedback-ios-scheduleDualNotification.md
Normal 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 plugin’s `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 app’s `buildDualScheduleConfig({ notifyTime })` and has the following shape.
|
||||
|
||||
---
|
||||
|
||||
## Config shape the app sends
|
||||
|
||||
The app sends a single `config` object that matches the plugin’s `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 user’s chosen time (e.g. 18:25 for notify at 18:30).
|
||||
- **userNotification.schedule:** The user’s 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 isn’t 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 plugin’s 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 plugin’s **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 app’s 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).
|
||||
Reference in New Issue
Block a user