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).
6.9 KiB
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
configobject that matches the plugin’sDualScheduleConfiguration(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
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):
{
"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 viaconfigureNativeFetcher). 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 fromuserNotification.
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)
- Accept the
configargument (object withcontentFetch,userNotification, and optionalrelationship). - Parse the cron expressions for
contentFetch.scheduleanduserNotification.schedule(e.g. using a shared cron parser or the same approach as Android). - Schedule two things:
- Content fetch: At the time given by
contentFetch.schedule, run the native notification content fetcher (the one configured viaconfigureNativeFetcher). 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 withinrelationship.contentTimeout; otherwise useuserNotification.titleanduserNotification.body(perrelationship.fallbackBehavior: "show_default").
- Content fetch: At the time given by
- Do not reject with
UNIMPLEMENTED; resolve the promise once scheduling has succeeded (or reject with a descriptive error if scheduling fails). - 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 forscheduleDualNotification(e.g. method that receivescall.getObject("config")). - Android reference:
android/implementation ofscheduleDualNotificationand 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 withcode: "UNIMPLEMENTED". - The content-fetch job is scheduled at
contentFetch.scheduleand uses the configured native fetcher to fetch content. - The user notification is scheduled at
userNotification.scheduleand shows with API-derived content when available, or withuserNotification.title/userNotification.bodyas 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:
configureNativeFetcher(...)on startup and when enabling New Activity.updateStarredPlans({ planIds })when enabling or when Account view loads with New Activity on.scheduleDualNotification({ config })when the user turns on New Activity and picks a time.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).