diff --git a/CHANGELOG.md b/CHANGELOG.md index 7674aad..1a83c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the Daily Notification Plugin will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Removed + +- **Breaking**: Retired `scheduleApiNotifications` / `scheduleApiNotification`. The plugin no longer creates or schedules new `api_*` notifications. `clearApiNotifications` remains for the host `legacyApiNotificationsCleanup` migration. + ## [4.0.1] - 2026-06-09 ### Fixed diff --git a/android/src/main/java/org/timesafari/dailynotification/DailyNotificationConstants.kt b/android/src/main/java/org/timesafari/dailynotification/DailyNotificationConstants.kt index dcf4b28..211b356 100644 --- a/android/src/main/java/org/timesafari/dailynotification/DailyNotificationConstants.kt +++ b/android/src/main/java/org/timesafari/dailynotification/DailyNotificationConstants.kt @@ -170,8 +170,8 @@ object DailyNotificationConstants { const val DUAL_NOTIFY_SCHEDULE_ID_PREFIX = "dual_notify_" /** - * Prefix for API-managed batch notification schedule IDs (`api_`). - * Matches iOS [apiNotificationPrefix]; must not overlap daily reminders or dual schedules. + * Prefix for leftover API-managed batch notification schedule IDs (`api_`). + * Used only by [ScheduleHelper.clearApiSchedules]; new schedules are no longer created in this namespace. */ const val API_SCHEDULE_ID_PREFIX = "api_" diff --git a/android/src/main/java/org/timesafari/dailynotification/DailyNotificationPlugin.kt b/android/src/main/java/org/timesafari/dailynotification/DailyNotificationPlugin.kt index cdf58cf..3763582 100644 --- a/android/src/main/java/org/timesafari/dailynotification/DailyNotificationPlugin.kt +++ b/android/src/main/java/org/timesafari/dailynotification/DailyNotificationPlugin.kt @@ -19,7 +19,6 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.Data import java.util.concurrent.TimeUnit import org.timesafari.dailynotification.DailyNotificationFetchWorker -import com.getcapacitor.JSArray import com.getcapacitor.JSObject import com.getcapacitor.Plugin import com.getcapacitor.PluginCall @@ -750,48 +749,9 @@ open class DailyNotificationPlugin : Plugin() { } /** - * Add one-shot API-managed notifications at epoch-ms timestamps (additive; does not clear others). - * Caller should invoke [clearApiNotifications] first when replacing a full batch. - */ - @PluginMethod - fun scheduleApiNotifications(call: PluginCall) { - val timestampsArray = call.getArray("timestamps") - if (timestampsArray == null) { - call.reject("Missing timestamps") - return - } - - CoroutineScope(Dispatchers.IO).launch { - try { - if (context == null) { - return@launch call.reject("Context not available") - } - Log.i(TAG, "DNP-BATCH: scheduleApiNotifications — additive scheduling for ${timestampsArray.length()} timestamp(s)") - - var scheduledCount = 0 - for (i in 0 until timestampsArray.length()) { - val raw = timestampsArray.opt(i) - val epochMillis = when (raw) { - is Number -> raw.toLong() - else -> continue - } - if (ScheduleHelper.scheduleApiNotification(context!!, getDatabase(), epochMillis)) { - scheduledCount++ - } - } - - Log.i(TAG, "DNP-BATCH: scheduleApiNotifications done scheduled=$scheduledCount/${timestampsArray.length()}") - call.resolve() - } catch (e: Exception) { - Log.e(TAG, "Failed to schedule notifications", e) - call.reject("Failed to schedule notifications: ${e.message}") - } - } - } - - /** - * Cancel only API-managed notification schedules (`api_*`). + * Cancel leftover API-managed notification schedules (`api_*`). * Does not affect Daily Reminders, user-created schedules, dual schedules, or fetch jobs. + * Kept for the host `legacyApiNotificationsCleanup` migration; this plugin no longer creates `api_*` schedules. */ @PluginMethod fun clearApiNotifications(call: PluginCall) { @@ -3011,62 +2971,6 @@ object ScheduleHelper { } } - /** - * Schedule one API-managed notification at an epoch-ms trigger time. - * Uses the api_ ID namespace so [clearApiSchedules] can replace batches safely. - * - * @return true when scheduled, false when timestamp is stale (in the past) - */ - suspend fun scheduleApiNotification( - context: Context, - database: DailyNotificationDatabase, - epochMillis: Long, - title: String = "Reminder", - body: String = "You have a scheduled notification" - ): Boolean { - if (epochMillis <= System.currentTimeMillis()) { - Log.d("ScheduleHelper", "DNP-BATCH: skip stale timestamp ts=$epochMillis (not in the future)") - return false - } - - // API-managed notifications use the api_ namespace so they can be safely - // replaced without affecting Daily Reminder schedules. - val scheduleId = "${DailyNotificationConstants.API_SCHEDULE_ID_PREFIX}${epochMillis}" - - val config = UserNotificationConfig( - enabled = true, - schedule = scheduleId, - title = title, - body = body, - sound = true, - vibration = true, - priority = "default" - ) - - // Persist before arming so NotifyReceiver updates this row, not an unrelated notify schedule. - createSchedule( - database, - Schedule( - id = scheduleId, - kind = "notify", - enabled = true, - nextRunAt = epochMillis - ) - ) - - NotifyReceiver.scheduleExactNotification( - context, - epochMillis, - config, - reminderId = scheduleId, - scheduleId = scheduleId, - source = ScheduleSource.INITIAL_SETUP - ) - - Log.i("ScheduleHelper", "DNP-BATCH: scheduled ts=$epochMillis id=$scheduleId") - return true - } - /** * Schedule user notification (alarm + database) * diff --git a/android/src/test/java/org/timesafari/dailynotification/ClearApiSchedulesTest.kt b/android/src/test/java/org/timesafari/dailynotification/ClearApiSchedulesTest.kt index 7d5c3f5..09f5115 100644 --- a/android/src/test/java/org/timesafari/dailynotification/ClearApiSchedulesTest.kt +++ b/android/src/test/java/org/timesafari/dailynotification/ClearApiSchedulesTest.kt @@ -31,21 +31,6 @@ class ClearApiSchedulesTest { database.close() } - @Test - fun scheduleApiNotification_usesApiIdNamespace() { - val epochMillis = System.currentTimeMillis() + 300_000 - val expectedId = "${DailyNotificationConstants.API_SCHEDULE_ID_PREFIX}${epochMillis}" - - val scheduled = runBlocking { - ScheduleHelper.scheduleApiNotification(context, database, epochMillis) - } - - assertEquals(true, scheduled) - runBlocking { - assertNotNull(database.scheduleDao().getById(expectedId)) - } - } - @Test fun clearApiSchedules_removesOnlyApiManagedAndPreservesDailyReminder() { val dailyReminderId = DailyNotificationConstants.DEFAULT_SCHEDULE_ID diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index 254a831..5f9c77e 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -32,7 +32,8 @@ public class DailyNotificationPlugin: CAPPlugin { // Background task identifiers private let fetchTaskIdentifier = "org.timesafari.dailynotification.fetch" private let notifyTaskIdentifier = "org.timesafari.dailynotification.notify" - /// Prefix for API-managed batch notification identifiers: `api_\(epochMillis)`. + /// Prefix for leftover API-managed batch notification identifiers: `api_\(epochMillis)`. + /// Used by `clearApiNotifications` and Daily Reminder legacy-id cancellation; new `api_*` requests are not scheduled. private let apiNotificationPrefix = "api_" /// Prefix for Daily Reminder UNNotificationRequest identifiers: `daily_\(logicalReminderId)`. private let dailyReminderNotificationPrefix = "daily_" @@ -1123,9 +1124,9 @@ public class DailyNotificationPlugin: CAPPlugin { notificationCenter.removePendingNotificationRequests(withIdentifiers: ids) } - // MARK: - API batch notifications (replace-all UNUserNotificationCenter) + // MARK: - Leftover API batch notifications (clear-only; scheduling retired) - /// Removes only pending and delivered API-managed notifications (`api_*`). Does not touch Daily Reminders (`daily_*`), dual/org IDs, or other stacks. + /// Removes only pending and delivered leftover API-managed notifications (`api_*`). Does not touch Daily Reminders (`daily_*`), dual/org IDs, or other stacks. @objc func clearApiNotifications(_ call: CAPPluginCall) { NSLog("DNP-BATCH: clearApiNotifications — removing api_* pending and delivered only") print("DNP-BATCH: clearApiNotifications — removing api_* pending and delivered only") @@ -1156,59 +1157,6 @@ public class DailyNotificationPlugin: CAPPlugin { } } - /// Add one-shot reminders at epoch-ms timestamps. Does not remove other requests; identical IDs replace pending entries. Caller should clear first if needed. - /// Adds/overwrites API-managed notifications using deterministic IDs. - /// Does NOT clear existing notifications. Caller is responsible for lifecycle. - @objc func scheduleApiNotifications(_ call: CAPPluginCall) { - guard let timestamps = call.getArray("timestamps", Double.self) else { - call.reject("Missing timestamps") - return - } - - NSLog("DNP-BATCH: scheduleApiNotifications — additive scheduling for \(timestamps.count) timestamp(s)") - print("DNP-BATCH: scheduleApiNotifications — additive scheduling for \(timestamps.count) timestamp(s)") - - for ts in timestamps { - let date = Date(timeIntervalSince1970: ts / 1000) - guard date.timeIntervalSinceNow > 0 else { - NSLog("DNP-BATCH: skip stale timestamp ts=\(ts) (not in the future)") - print("DNP-BATCH: skip stale timestamp ts=\(ts) (not in the future)") - continue - } - - let interval = date.timeIntervalSinceNow - - let trigger = UNTimeIntervalNotificationTrigger( - timeInterval: interval, - repeats: false - ) - - let content = UNMutableNotificationContent() - content.title = "Reminder" - content.body = "You have a scheduled notification" - - let id = "\(apiNotificationPrefix)\(Int(ts))" - - NSLog("DNP-BATCH: scheduling ts=\(ts) interval=\(interval)s id=\(id)") - print("DNP-BATCH: scheduling ts=\(ts) interval=\(interval)s id=\(id)") - - let request = UNNotificationRequest( - identifier: id, - content: content, - trigger: trigger - ) - - notificationCenter.add(request) { error in - if let error = error { - NSLog("DNP-BATCH: add failed id=\(id) error=\(error.localizedDescription)") - print("DNP-BATCH: add failed id=\(id) error=\(error.localizedDescription)") - } - } - } - - call.resolve() - } - // MARK: - Static Daily Reminder Methods @objc func scheduleDailyReminder(_ call: CAPPluginCall) { @@ -1712,7 +1660,7 @@ public class DailyNotificationPlugin: CAPPlugin { let fireDate = Date().addingTimeInterval(TimeInterval(validSeconds)) let epochMillis = Int64(fireDate.timeIntervalSince1970 * 1000) - let requestId = apiNotificationId(epochMillis: epochMillis) + let requestId = "test_\(epochMillis)" let request = UNNotificationRequest( identifier: requestId, content: notificationContent, @@ -2803,9 +2751,8 @@ extension DailyNotificationPlugin { methods.append(CAPPluginMethod(name: "getScheduledReminders", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "updateDailyReminder", returnType: CAPPluginReturnPromise)) - // API batch notifications (replace-all UNUserNotificationCenter) + // Leftover API batch notifications (clear-only; scheduling retired) methods.append(CAPPluginMethod(name: "clearApiNotifications", returnType: CAPPluginReturnPromise)) - methods.append(CAPPluginMethod(name: "scheduleApiNotifications", returnType: CAPPluginReturnPromise)) // Dual scheduling methods methods.append(CAPPluginMethod(name: "scheduleContentFetch", returnType: CAPPluginReturnPromise)) diff --git a/src/definitions.ts b/src/definitions.ts index 3fec41e..95034ec 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -603,14 +603,12 @@ export interface DailyNotificationPlugin { getLastNotification(): Promise; cancelAllNotifications(): Promise; /** - * Add one-shot API-managed notifications at epoch-ms timestamps (additive). - * Uses `api_` identifiers; caller should clear first when replacing a batch. - */ - scheduleApiNotifications(options: { timestamps: number[] }): Promise; - /** - * Cancel API-managed notifications (`api_*`) without affecting Daily Reminders + * Cancel leftover API-managed notifications (`api_*`) without affecting Daily Reminders * (iOS: `daily_*`; Android: `daily_notification` / caller id), user schedules, * dual schedules, or fetch jobs. + * + * Kept for the one-time `legacyApiNotificationsCleanup` host migration. + * New `api_*` notifications are no longer created or scheduled by this plugin. */ clearApiNotifications(): Promise; getNotificationStatus(): Promise; diff --git a/src/web.ts b/src/web.ts index fda08ba..f25a264 100644 --- a/src/web.ts +++ b/src/web.ts @@ -118,10 +118,6 @@ export class DailyNotificationWeb implements DailyNotificationPlugin { this.throwNotSupported(); } - async scheduleApiNotifications(): Promise { - this.throwNotSupported(); - } - async clearApiNotifications(): Promise { this.throwNotSupported(); }