forked from trent_larson/crowd-funder-for-time-pwa
- buildDualScheduleConfig: set contentFetch timeout/retryAttempts/retryDelay (match capacitor DailyNotification networkConfig), userNotification.vibration, return type DualScheduleConfiguration - @timesafari/daily-notification-plugin 2.1.1 → 2.1.3 (package-lock) - doc: plugin feedback (contentFetch JSON, parseUserNotificationConfig optional fields) and Android DailyNotificationWorker duplicate scheduleId note
118 lines
6.1 KiB
Markdown
118 lines
6.1 KiB
Markdown
# Plugin feedback: Android `scheduleDualNotification` — `JSONException: No value for timeout`
|
||
|
||
**Date:** 2026-03-20 18:21 PST
|
||
**Target repo:** `@timesafari/daily-notification-plugin` (daily-notification-plugin)
|
||
**Consuming app:** crowd-funder-for-time-pwa (TimeSafari)
|
||
**Platform:** Android (Kotlin)
|
||
**Plugin version observed:** 2.1.2 (from app `node_modules`)
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
Scheduling the **New Activity** dual notification on Android fails with a native `JSONException` because `DailyNotificationPlugin.parseContentFetchConfig()` uses **`JSONObject.getInt()`** for `timeout`, `retryAttempts`, and `retryDelay`. Those keys are **absent** from the app’s `contentFetch` object built by `buildDualScheduleConfig()`. The plugin’s own TypeScript `ContentFetchConfig` marks those fields as **optional**, so the Android parser is stricter than the published contract.
|
||
|
||
**Recommended direction:**
|
||
|
||
1. **Plugin (primary):** Parse optional numeric fields with defaults (e.g. `optInt` / nullable + defaults) so payloads that omit them do not crash and match `definitions.d.ts`.
|
||
2. **App (secondary / compatibility):** Include explicit `timeout`, `retryAttempts`, and `retryDelay` on `contentFetch` so older plugin versions that still use `getInt` continue to work.
|
||
|
||
**Does it make sense to change both sides?** **Yes.** Fixing the plugin aligns behavior with the documented API and protects any consumer that omits those fields. Fixing the app is still valuable for **older shipped plugin builds** and makes network behavior explicit. Together you get backward compatibility, clearer intent, and no silent reliance on undocumented defaults.
|
||
|
||
---
|
||
|
||
## Symptoms (consuming app)
|
||
|
||
- In-app toast: *“Could not schedule New Activity notification. Please try again.”* (generic error path after `scheduleDualNotification` rejects.)
|
||
- Logcat (filtered on DNP / plugin tags):
|
||
|
||
```text
|
||
E DNP-PLUGIN: Schedule dual notification error
|
||
E DNP-PLUGIN: org.json.JSONException: No value for timeout
|
||
E DNP-PLUGIN: at org.json.JSONObject.getInt(JSONObject.java:487)
|
||
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.parseContentFetchConfig(DailyNotificationPlugin.kt:2403)
|
||
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.scheduleDualNotification(DailyNotificationPlugin.kt:1391)
|
||
```
|
||
|
||
---
|
||
|
||
## Root cause
|
||
|
||
### Call path
|
||
|
||
`scheduleDualNotification` reads `config.contentFetch` and passes it to `parseContentFetchConfig`:
|
||
|
||
- File: `android/.../DailyNotificationPlugin.kt`
|
||
- `scheduleDualNotification` ~1391: `parseContentFetchConfig(contentFetchObj)`
|
||
- `parseContentFetchConfig` ~2397–2411: uses `getInt` for three keys.
|
||
|
||
### Strict Android parsing
|
||
|
||
Illustrative (exact line numbers may shift between releases):
|
||
|
||
```kotlin
|
||
// parseContentFetchConfig — timeout / retry fields are required via getInt()
|
||
timeout = configJson.getInt("timeout"),
|
||
retryAttempts = configJson.getInt("retryAttempts"),
|
||
retryDelay = configJson.getInt("retryDelay"),
|
||
```
|
||
|
||
`getInt` throws if the key is missing → first missing key in practice is `timeout` → `JSONException: No value for timeout`.
|
||
|
||
### App payload today (consuming app)
|
||
|
||
File: `src/services/notifications/dualScheduleConfig.ts` — `buildDualScheduleConfig()` sets `contentFetch` to:
|
||
|
||
- `enabled`, `schedule`, `callbacks` only (no `timeout`, `retryAttempts`, `retryDelay`, no `url`).
|
||
|
||
That matches the **TypeScript** contract in the plugin’s `dist/esm/definitions.d.ts`, where `timeout`, `retryAttempts`, and `retryDelay` are **optional** on `ContentFetchConfig`.
|
||
|
||
### Contract mismatch
|
||
|
||
| Layer | `timeout` / `retryAttempts` / `retryDelay` |
|
||
|--------|--------------------------------------------|
|
||
| TS `ContentFetchConfig` | Optional (`?`) |
|
||
| Android `parseContentFetchConfig` | Required (`getInt` — throws if absent) |
|
||
|
||
The consuming app followed the TS API; Android rejected it at runtime.
|
||
|
||
---
|
||
|
||
## Plugin-side recommendations
|
||
|
||
1. **Use optional reads with defaults** for `timeout`, `retryAttempts`, and `retryDelay` (and any similar fields), e.g. Kotlin/Capacitor equivalents of `optInt` or `getInteger` with fallbacks documented in `ContentFetchConfig`.
|
||
2. **Document defaults** in the plugin README or API docs if they are applied on native when omitted.
|
||
3. **Consider tests** that call `scheduleDualNotification` with a minimal `contentFetch` (only `enabled`, `schedule`, `callbacks`) and assert scheduling succeeds on Android.
|
||
4. **Optional:** If `url` is also read in a way that assumes presence, align with TS (`url?`) the same way.
|
||
|
||
---
|
||
|
||
## App-side recommendations (later; crowd-funder-for-time-pwa)
|
||
|
||
When you implement the app fix:
|
||
|
||
- Extend `contentFetch` in `buildDualScheduleConfig()` (`src/services/notifications/dualScheduleConfig.ts`) to include explicit integers, for example aligned with existing app/network conventions (the app’s `capacitor.config.ts` already uses a `timeout` value in one place — reuse or document chosen values).
|
||
- Ensure **both** code paths that build dual config stay in sync (e.g. `AccountViewView.vue` uses `buildDualScheduleConfig` for New Activity scheduling and for `updateDualScheduleConfig` fallback).
|
||
|
||
This unblocks users on **current** plugin versions that still require those keys.
|
||
|
||
---
|
||
|
||
## References (paths in consuming app workspace)
|
||
|
||
- App config builder: `src/services/notifications/dualScheduleConfig.ts`
|
||
- Native scheduling entry: `node_modules/@timesafari/daily-notification-plugin/android/.../DailyNotificationPlugin.kt` (`scheduleDualNotification`, `parseContentFetchConfig`)
|
||
|
||
---
|
||
|
||
## Answer: change both plugin and app?
|
||
|
||
**Yes, it makes sense to change both**, for different reasons:
|
||
|
||
| Side | Why |
|
||
|------|-----|
|
||
| **Plugin** | Fixes the real bug: native behavior must match the published optional TS fields; avoids breaking any client that sends a minimal `contentFetch`. |
|
||
| **App** | Defense in depth and support for **already-shipped** plugin binaries that will not get the Kotlin fix until users update the app. Explicit values also document intended fetch/retry behavior in one place. |
|
||
|
||
If you only fix the plugin, new app releases still need users to update the **native** binary. If you only fix the app, any other consumer of the plugin or future minimal payloads can hit the same crash until the plugin is fixed.
|