capacitor-mobilecron 0.2.22 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,6 +3,8 @@ package io.mobilecron
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.Intent
|
|
5
5
|
import android.content.IntentFilter
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
6
8
|
import androidx.work.Constraints
|
|
7
9
|
import androidx.work.ExistingPeriodicWorkPolicy
|
|
8
10
|
import androidx.work.ExistingWorkPolicy
|
|
@@ -29,27 +31,55 @@ class MobileCronPlugin : Plugin() {
|
|
|
29
31
|
private var mode = "balanced"
|
|
30
32
|
private var chargingReceiver: ChargingReceiver? = null
|
|
31
33
|
|
|
34
|
+
// Foreground watchdog — evaluates due jobs while the app is active
|
|
35
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
36
|
+
private var watchdogRunning = false
|
|
37
|
+
private val watchdogRunnable = object : Runnable {
|
|
38
|
+
override fun run() {
|
|
39
|
+
if (!watchdogRunning) return
|
|
40
|
+
evaluateDueJobsForeground()
|
|
41
|
+
handler.postDelayed(this, tickMs())
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
companion object {
|
|
33
46
|
private const val STORAGE_FILE = "CapacitorStorage"
|
|
34
47
|
private const val STORAGE_KEY = "mobilecron:state"
|
|
48
|
+
private const val TICK_ECO = 60_000L
|
|
49
|
+
private const val TICK_BALANCED = 30_000L
|
|
50
|
+
private const val TICK_AGGRESSIVE = 15_000L
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private fun tickMs(): Long = when (mode) {
|
|
54
|
+
"eco" -> TICK_ECO
|
|
55
|
+
"aggressive" -> TICK_AGGRESSIVE
|
|
56
|
+
else -> TICK_BALANCED
|
|
35
57
|
}
|
|
36
58
|
|
|
37
59
|
override fun load() {
|
|
38
60
|
super.load()
|
|
39
61
|
CronBridge.plugin = this
|
|
40
62
|
loadState()
|
|
63
|
+
computeMissingNextDueAt()
|
|
41
64
|
registerChargingReceiver()
|
|
42
65
|
scheduleWorkManager()
|
|
66
|
+
startWatchdog()
|
|
43
67
|
}
|
|
44
68
|
|
|
45
69
|
override fun handleOnResume() {
|
|
46
70
|
super.handleOnResume()
|
|
47
|
-
// Fire any pendingNativeEvents written by NativeJobEvaluator while the app was backgrounded/killed.
|
|
48
71
|
firePendingNativeEvents()
|
|
72
|
+
startWatchdog()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun handleOnPause() {
|
|
76
|
+
super.handleOnPause()
|
|
77
|
+
stopWatchdog()
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
override fun handleOnDestroy() {
|
|
52
81
|
super.handleOnDestroy()
|
|
82
|
+
stopWatchdog()
|
|
53
83
|
if (CronBridge.plugin === this) {
|
|
54
84
|
CronBridge.plugin = null
|
|
55
85
|
}
|
|
@@ -59,6 +89,115 @@ class MobileCronPlugin : Plugin() {
|
|
|
59
89
|
chargingReceiver = null
|
|
60
90
|
}
|
|
61
91
|
|
|
92
|
+
// ── Foreground watchdog ───────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
private fun startWatchdog() {
|
|
95
|
+
if (watchdogRunning) return
|
|
96
|
+
watchdogRunning = true
|
|
97
|
+
handler.postDelayed(watchdogRunnable, tickMs())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private fun stopWatchdog() {
|
|
101
|
+
watchdogRunning = false
|
|
102
|
+
handler.removeCallbacks(watchdogRunnable)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun restartWatchdog() {
|
|
106
|
+
stopWatchdog()
|
|
107
|
+
startWatchdog()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate due jobs while the app is in the foreground.
|
|
112
|
+
* Directly emits jobDue events via notifyListeners (no SharedPreferences round-trip).
|
|
113
|
+
* Mirrors the logic in NativeJobEvaluator but operates on the in-memory jobs map.
|
|
114
|
+
*/
|
|
115
|
+
private fun evaluateDueJobsForeground() {
|
|
116
|
+
val now = System.currentTimeMillis()
|
|
117
|
+
var mutated = false
|
|
118
|
+
|
|
119
|
+
for ((id, job) in jobs) {
|
|
120
|
+
if (!job.optBoolean("enabled", false)) continue
|
|
121
|
+
|
|
122
|
+
// Compute nextDueAt if missing
|
|
123
|
+
val schedule = try { job.getJSONObject("schedule") } catch (_: Exception) { continue }
|
|
124
|
+
var nextDueAt = readLong(job.opt("nextDueAt"))
|
|
125
|
+
if (nextDueAt == null) {
|
|
126
|
+
nextDueAt = NativeJobEvaluator.computeNextDueAt(schedule, now)
|
|
127
|
+
if (nextDueAt != null) {
|
|
128
|
+
job.put("nextDueAt", nextDueAt)
|
|
129
|
+
mutated = true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (nextDueAt == null || nextDueAt > now) continue
|
|
134
|
+
|
|
135
|
+
// Check skip reasons
|
|
136
|
+
if (paused) continue
|
|
137
|
+
|
|
138
|
+
val activeHours = try { job.getJSONObject("activeHours") } catch (_: Exception) { null }
|
|
139
|
+
if (activeHours != null && !NativeJobEvaluator.isWithinActiveHours(activeHours, now)) {
|
|
140
|
+
job.put("consecutiveSkips", job.optInt("consecutiveSkips", 0) + 1)
|
|
141
|
+
job.put("updatedAt", now)
|
|
142
|
+
if (schedule.optString("kind") == "every") {
|
|
143
|
+
val next = NativeJobEvaluator.computeNextDueAt(schedule, now)
|
|
144
|
+
if (next != null) job.put("nextDueAt", next) else job.remove("nextDueAt")
|
|
145
|
+
}
|
|
146
|
+
mutated = true
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Fire the job
|
|
151
|
+
job.put("lastFiredAt", now)
|
|
152
|
+
job.put("updatedAt", now)
|
|
153
|
+
job.put("consecutiveSkips", 0)
|
|
154
|
+
|
|
155
|
+
if (schedule.optString("kind") == "at") {
|
|
156
|
+
job.put("enabled", false)
|
|
157
|
+
job.remove("nextDueAt")
|
|
158
|
+
} else {
|
|
159
|
+
val next = NativeJobEvaluator.computeNextDueAt(schedule, now)
|
|
160
|
+
if (next != null) job.put("nextDueAt", next) else job.remove("nextDueAt")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
mutated = true
|
|
164
|
+
|
|
165
|
+
// Emit jobDue directly to JS listeners
|
|
166
|
+
val payload = JSObject()
|
|
167
|
+
payload.put("id", id)
|
|
168
|
+
payload.put("name", job.optString("name"))
|
|
169
|
+
payload.put("firedAt", now)
|
|
170
|
+
payload.put("source", "watchdog")
|
|
171
|
+
if (job.has("data")) {
|
|
172
|
+
try { payload.put("data", job.getJSONObject("data")) } catch (_: Exception) {}
|
|
173
|
+
}
|
|
174
|
+
notifyListeners("jobDue", payload)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (mutated) {
|
|
178
|
+
saveState()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Compute nextDueAt for all jobs that are missing it (e.g. after loadState from storage).
|
|
184
|
+
*/
|
|
185
|
+
private fun computeMissingNextDueAt() {
|
|
186
|
+
val now = System.currentTimeMillis()
|
|
187
|
+
var mutated = false
|
|
188
|
+
for ((_, job) in jobs) {
|
|
189
|
+
if (!job.optBoolean("enabled", false)) continue
|
|
190
|
+
if (readLong(job.opt("nextDueAt")) != null) continue
|
|
191
|
+
val schedule = try { job.getJSONObject("schedule") } catch (_: Exception) { continue }
|
|
192
|
+
val computed = NativeJobEvaluator.computeNextDueAt(schedule, now)
|
|
193
|
+
if (computed != null) {
|
|
194
|
+
job.put("nextDueAt", computed)
|
|
195
|
+
mutated = true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (mutated) saveState()
|
|
199
|
+
}
|
|
200
|
+
|
|
62
201
|
// ── Persistence ─────────────────────────────────────────────────────────
|
|
63
202
|
|
|
64
203
|
private fun loadState() {
|
|
@@ -200,17 +339,29 @@ class MobileCronPlugin : Plugin() {
|
|
|
200
339
|
}
|
|
201
340
|
|
|
202
341
|
val id = UUID.randomUUID().toString()
|
|
342
|
+
val now = System.currentTimeMillis()
|
|
343
|
+
val schedule = call.getObject("schedule") ?: JSObject()
|
|
344
|
+
|
|
203
345
|
val record = JSObject()
|
|
204
346
|
record.put("id", id)
|
|
205
347
|
record.put("name", name)
|
|
206
348
|
record.put("enabled", true)
|
|
207
|
-
record.put("schedule",
|
|
349
|
+
record.put("schedule", schedule)
|
|
208
350
|
record.put("activeHours", call.getObject("activeHours"))
|
|
209
351
|
record.put("requiresNetwork", call.getBoolean("requiresNetwork", false))
|
|
210
352
|
record.put("requiresCharging", call.getBoolean("requiresCharging", false))
|
|
211
353
|
record.put("priority", call.getString("priority", "normal"))
|
|
212
354
|
call.getObject("data")?.let { record.put("data", it) }
|
|
213
355
|
record.put("consecutiveSkips", 0)
|
|
356
|
+
record.put("createdAt", now)
|
|
357
|
+
record.put("updatedAt", now)
|
|
358
|
+
|
|
359
|
+
// Compute nextDueAt at registration time
|
|
360
|
+
val nextDueAt = NativeJobEvaluator.computeNextDueAt(schedule, now)
|
|
361
|
+
if (nextDueAt != null) {
|
|
362
|
+
record.put("nextDueAt", nextDueAt)
|
|
363
|
+
}
|
|
364
|
+
|
|
214
365
|
jobs[id] = record
|
|
215
366
|
saveState()
|
|
216
367
|
|
|
@@ -247,12 +398,18 @@ class MobileCronPlugin : Plugin() {
|
|
|
247
398
|
}
|
|
248
399
|
|
|
249
400
|
call.getString("name")?.let { existing.put("name", it) }
|
|
250
|
-
call.getObject("schedule")?.let {
|
|
401
|
+
call.getObject("schedule")?.let {
|
|
402
|
+
existing.put("schedule", it)
|
|
403
|
+
// Recompute nextDueAt when schedule changes
|
|
404
|
+
val next = NativeJobEvaluator.computeNextDueAt(it, System.currentTimeMillis())
|
|
405
|
+
if (next != null) existing.put("nextDueAt", next) else existing.remove("nextDueAt")
|
|
406
|
+
}
|
|
251
407
|
if (call.data.has("activeHours")) existing.put("activeHours", call.getObject("activeHours"))
|
|
252
408
|
if (call.data.has("requiresNetwork")) existing.put("requiresNetwork", call.getBoolean("requiresNetwork", false))
|
|
253
409
|
if (call.data.has("requiresCharging")) existing.put("requiresCharging", call.getBoolean("requiresCharging", false))
|
|
254
410
|
call.getString("priority")?.let { existing.put("priority", it) }
|
|
255
411
|
if (call.data.has("data")) existing.put("data", call.getObject("data"))
|
|
412
|
+
existing.put("updatedAt", System.currentTimeMillis())
|
|
256
413
|
|
|
257
414
|
jobs[id] = existing
|
|
258
415
|
saveState()
|
|
@@ -318,6 +475,7 @@ class MobileCronPlugin : Plugin() {
|
|
|
318
475
|
mode = next!!
|
|
319
476
|
scheduleWorkManager()
|
|
320
477
|
saveState()
|
|
478
|
+
restartWatchdog()
|
|
321
479
|
call.resolve()
|
|
322
480
|
notifyListeners("statusChanged", buildStatus())
|
|
323
481
|
}
|
|
@@ -339,4 +497,13 @@ class MobileCronPlugin : Plugin() {
|
|
|
339
497
|
})
|
|
340
498
|
return status
|
|
341
499
|
}
|
|
500
|
+
|
|
501
|
+
private fun readLong(value: Any?): Long? {
|
|
502
|
+
return when (value) {
|
|
503
|
+
null -> null
|
|
504
|
+
is Number -> value.toLong()
|
|
505
|
+
is String -> value.toLongOrNull()
|
|
506
|
+
else -> null
|
|
507
|
+
}
|
|
508
|
+
}
|
|
342
509
|
}
|
|
@@ -28,11 +28,23 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
28
28
|
var currentMode: String { mode }
|
|
29
29
|
private var bgManager: BGTaskManager?
|
|
30
30
|
|
|
31
|
+
// Foreground watchdog — evaluates due jobs while the app is active
|
|
32
|
+
private var watchdogTimer: Timer?
|
|
33
|
+
|
|
34
|
+
private var tickInterval: TimeInterval {
|
|
35
|
+
switch mode {
|
|
36
|
+
case "eco": return 60.0
|
|
37
|
+
case "aggressive": return 15.0
|
|
38
|
+
default: return 30.0
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
32
43
|
|
|
33
44
|
public override func load() {
|
|
34
45
|
super.load()
|
|
35
46
|
loadState()
|
|
47
|
+
computeMissingNextDueAt()
|
|
36
48
|
firePendingNativeEvents()
|
|
37
49
|
let manager = BGTaskManager(plugin: self)
|
|
38
50
|
manager.registerBGTasks()
|
|
@@ -45,10 +57,22 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
57
|
name: UIApplication.didBecomeActiveNotification,
|
|
46
58
|
object: nil
|
|
47
59
|
)
|
|
60
|
+
NotificationCenter.default.addObserver(
|
|
61
|
+
self,
|
|
62
|
+
selector: #selector(appWillResignActive),
|
|
63
|
+
name: UIApplication.willResignActiveNotification,
|
|
64
|
+
object: nil
|
|
65
|
+
)
|
|
66
|
+
startWatchdog()
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
@objc private func appDidBecomeActive() {
|
|
51
70
|
firePendingNativeEvents()
|
|
71
|
+
startWatchdog()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc private func appWillResignActive() {
|
|
75
|
+
stopWatchdog()
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
func handleBackgroundWake(source: String) {
|
|
@@ -57,6 +81,121 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
57
81
|
notifyListeners("nativeWake", data: ["source": source, "paused": paused])
|
|
58
82
|
}
|
|
59
83
|
|
|
84
|
+
// ── Foreground watchdog ───────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
private func startWatchdog() {
|
|
87
|
+
guard watchdogTimer == nil else { return }
|
|
88
|
+
watchdogTimer = Timer.scheduledTimer(withTimeInterval: tickInterval, repeats: true) { [weak self] _ in
|
|
89
|
+
self?.evaluateDueJobsForeground()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func stopWatchdog() {
|
|
94
|
+
watchdogTimer?.invalidate()
|
|
95
|
+
watchdogTimer = nil
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private func restartWatchdog() {
|
|
99
|
+
stopWatchdog()
|
|
100
|
+
startWatchdog()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Evaluate due jobs while the app is in the foreground.
|
|
104
|
+
/// Directly emits jobDue events via notifyListeners (no file round-trip).
|
|
105
|
+
private func evaluateDueJobsForeground() {
|
|
106
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
107
|
+
var mutated = false
|
|
108
|
+
|
|
109
|
+
for (id, var job) in jobs {
|
|
110
|
+
guard (job["enabled"] as? Bool) == true else { continue }
|
|
111
|
+
guard let schedule = job["schedule"] as? [String: Any] else { continue }
|
|
112
|
+
|
|
113
|
+
// Compute nextDueAt if missing
|
|
114
|
+
var nextDueAt = readLong(job["nextDueAt"])
|
|
115
|
+
if nextDueAt == nil {
|
|
116
|
+
if let computed = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
117
|
+
nextDueAt = computed
|
|
118
|
+
job["nextDueAt"] = computed
|
|
119
|
+
jobs[id] = job
|
|
120
|
+
mutated = true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
guard let due = nextDueAt, due <= now else { continue }
|
|
125
|
+
|
|
126
|
+
// Check skip reasons
|
|
127
|
+
if paused { continue }
|
|
128
|
+
|
|
129
|
+
if let activeHours = job["activeHours"] as? [String: Any],
|
|
130
|
+
!NativeJobEvaluator.isWithinActiveHours(activeHours: activeHours, nowMs: now) {
|
|
131
|
+
job["consecutiveSkips"] = ((job["consecutiveSkips"] as? Int) ?? 0) + 1
|
|
132
|
+
job["updatedAt"] = now
|
|
133
|
+
if (schedule["kind"] as? String) == "every" {
|
|
134
|
+
if let next = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
135
|
+
job["nextDueAt"] = next
|
|
136
|
+
} else {
|
|
137
|
+
job.removeValue(forKey: "nextDueAt")
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
jobs[id] = job
|
|
141
|
+
mutated = true
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fire the job
|
|
146
|
+
job["lastFiredAt"] = now
|
|
147
|
+
job["updatedAt"] = now
|
|
148
|
+
job["consecutiveSkips"] = 0
|
|
149
|
+
|
|
150
|
+
if (schedule["kind"] as? String) == "at" {
|
|
151
|
+
job["enabled"] = false
|
|
152
|
+
job.removeValue(forKey: "nextDueAt")
|
|
153
|
+
} else {
|
|
154
|
+
if let next = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
155
|
+
job["nextDueAt"] = next
|
|
156
|
+
} else {
|
|
157
|
+
job.removeValue(forKey: "nextDueAt")
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
jobs[id] = job
|
|
162
|
+
mutated = true
|
|
163
|
+
|
|
164
|
+
// Emit jobDue directly to JS listeners
|
|
165
|
+
var payload: [String: Any] = [
|
|
166
|
+
"id": id,
|
|
167
|
+
"name": (job["name"] as? String) ?? "",
|
|
168
|
+
"firedAt": now,
|
|
169
|
+
"source": "watchdog"
|
|
170
|
+
]
|
|
171
|
+
if let data = job["data"] {
|
|
172
|
+
payload["data"] = data
|
|
173
|
+
}
|
|
174
|
+
notifyListeners("jobDue", data: payload)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if mutated {
|
|
178
|
+
saveState()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Compute nextDueAt for all jobs that are missing it (e.g. after loadState from storage).
|
|
183
|
+
private func computeMissingNextDueAt() {
|
|
184
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
185
|
+
var mutated = false
|
|
186
|
+
for (id, var job) in jobs {
|
|
187
|
+
guard (job["enabled"] as? Bool) == true else { continue }
|
|
188
|
+
guard readLong(job["nextDueAt"]) == nil else { continue }
|
|
189
|
+
guard let schedule = job["schedule"] as? [String: Any] else { continue }
|
|
190
|
+
if let computed = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
191
|
+
job["nextDueAt"] = computed
|
|
192
|
+
jobs[id] = job
|
|
193
|
+
mutated = true
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if mutated { saveState() }
|
|
197
|
+
}
|
|
198
|
+
|
|
60
199
|
// ── Persistence ───────────────────────────────────────────────────────────
|
|
61
200
|
// Uses NativeJobEvaluator.readStateRaw() / writeStateRaw() for file-backed
|
|
62
201
|
// atomic writes that survive simctl terminate (SIGKILL) reliably.
|
|
@@ -144,19 +283,29 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
144
283
|
}
|
|
145
284
|
|
|
146
285
|
let id = UUID().uuidString
|
|
286
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
287
|
+
let schedule = call.getObject("schedule") ?? [:]
|
|
288
|
+
|
|
147
289
|
var record: [String: Any] = [
|
|
148
290
|
"id": id,
|
|
149
291
|
"name": name,
|
|
150
292
|
"enabled": true,
|
|
151
|
-
"consecutiveSkips": 0
|
|
293
|
+
"consecutiveSkips": 0,
|
|
294
|
+
"createdAt": now,
|
|
295
|
+
"updatedAt": now
|
|
152
296
|
]
|
|
153
|
-
|
|
297
|
+
record["schedule"] = schedule
|
|
154
298
|
if let activeHours = call.getObject("activeHours") { record["activeHours"] = activeHours }
|
|
155
299
|
if call.options.keys.contains("requiresNetwork") { record["requiresNetwork"] = call.getBool("requiresNetwork") ?? false }
|
|
156
300
|
if call.options.keys.contains("requiresCharging") { record["requiresCharging"] = call.getBool("requiresCharging") ?? false }
|
|
157
301
|
if let priority = call.getString("priority") { record["priority"] = priority }
|
|
158
302
|
if let data = call.getObject("data") { record["data"] = data }
|
|
159
303
|
|
|
304
|
+
// Compute nextDueAt at registration time
|
|
305
|
+
if let nextDueAt = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
306
|
+
record["nextDueAt"] = nextDueAt
|
|
307
|
+
}
|
|
308
|
+
|
|
160
309
|
jobs[id] = record
|
|
161
310
|
saveState()
|
|
162
311
|
notifyListeners("statusChanged", data: buildStatus())
|
|
@@ -185,12 +334,22 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
185
334
|
}
|
|
186
335
|
|
|
187
336
|
if let name = call.getString("name") { existing["name"] = name }
|
|
188
|
-
if let schedule = call.getObject("schedule") {
|
|
337
|
+
if let schedule = call.getObject("schedule") {
|
|
338
|
+
existing["schedule"] = schedule
|
|
339
|
+
// Recompute nextDueAt when schedule changes
|
|
340
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
341
|
+
if let next = NativeJobEvaluator.computeNextDueAt(schedule: schedule, nowMs: now) {
|
|
342
|
+
existing["nextDueAt"] = next
|
|
343
|
+
} else {
|
|
344
|
+
existing.removeValue(forKey: "nextDueAt")
|
|
345
|
+
}
|
|
346
|
+
}
|
|
189
347
|
if call.options.keys.contains("activeHours") { existing["activeHours"] = call.getObject("activeHours") }
|
|
190
348
|
if call.options.keys.contains("requiresNetwork") { existing["requiresNetwork"] = call.getBool("requiresNetwork") ?? false }
|
|
191
349
|
if call.options.keys.contains("requiresCharging") { existing["requiresCharging"] = call.getBool("requiresCharging") ?? false }
|
|
192
350
|
if let priority = call.getString("priority") { existing["priority"] = priority }
|
|
193
351
|
if call.options.keys.contains("data") { existing["data"] = call.getObject("data") }
|
|
352
|
+
existing["updatedAt"] = Int64(Date().timeIntervalSince1970 * 1000)
|
|
194
353
|
|
|
195
354
|
jobs[id] = existing
|
|
196
355
|
saveState()
|
|
@@ -250,6 +409,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
250
409
|
bgManager.scheduleProcessing(requiresExternalPower: mode != "aggressive")
|
|
251
410
|
}
|
|
252
411
|
saveState()
|
|
412
|
+
restartWatchdog()
|
|
253
413
|
notifyListeners("statusChanged", data: buildStatus())
|
|
254
414
|
call.resolve()
|
|
255
415
|
}
|
|
@@ -331,4 +491,12 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
331
491
|
]
|
|
332
492
|
]
|
|
333
493
|
}
|
|
494
|
+
|
|
495
|
+
private func readLong(_ value: Any?) -> Int64? {
|
|
496
|
+
switch value {
|
|
497
|
+
case let n as NSNumber: return n.int64Value
|
|
498
|
+
case let s as String: return Int64(s)
|
|
499
|
+
default: return nil
|
|
500
|
+
}
|
|
501
|
+
}
|
|
334
502
|
}
|
package/package.json
CHANGED