remove scheduleApiNotifications so the plugin cannot create new api_* schedules
This commit is contained in:
@@ -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
|
||||
|
||||
+2
-2
@@ -170,8 +170,8 @@ object DailyNotificationConstants {
|
||||
const val DUAL_NOTIFY_SCHEDULE_ID_PREFIX = "dual_notify_"
|
||||
|
||||
/**
|
||||
* Prefix for API-managed batch notification schedule IDs (`api_<epochMillis>`).
|
||||
* Matches iOS [apiNotificationPrefix]; must not overlap daily reminders or dual schedules.
|
||||
* Prefix for leftover API-managed batch notification schedule IDs (`api_<epochMillis>`).
|
||||
* Used only by [ScheduleHelper.clearApiSchedules]; new schedules are no longer created in this namespace.
|
||||
*/
|
||||
const val API_SCHEDULE_ID_PREFIX = "api_"
|
||||
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
+4
-6
@@ -603,14 +603,12 @@ export interface DailyNotificationPlugin {
|
||||
getLastNotification(): Promise<NotificationResponse | null>;
|
||||
cancelAllNotifications(): Promise<void>;
|
||||
/**
|
||||
* Add one-shot API-managed notifications at epoch-ms timestamps (additive).
|
||||
* Uses `api_<timestamp>` identifiers; caller should clear first when replacing a batch.
|
||||
*/
|
||||
scheduleApiNotifications(options: { timestamps: number[] }): Promise<void>;
|
||||
/**
|
||||
* 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<void>;
|
||||
getNotificationStatus(): Promise<NotificationStatus>;
|
||||
|
||||
@@ -118,10 +118,6 @@ export class DailyNotificationWeb implements DailyNotificationPlugin {
|
||||
this.throwNotSupported();
|
||||
}
|
||||
|
||||
async scheduleApiNotifications(): Promise<void> {
|
||||
this.throwNotSupported();
|
||||
}
|
||||
|
||||
async clearApiNotifications(): Promise<void> {
|
||||
this.throwNotSupported();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user