capacitor-mobilecron 0.2.1 → 0.2.3
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.
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(File.dirname(__FILE__), 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapacitorMobilecron'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = 'https://github.com/rogelioRuiz/capacitor-mobilecron'
|
|
11
|
+
s.author = 'Rogelio Ruiz'
|
|
12
|
+
s.source = { :git => 'https://github.com/rogelioRuiz/capacitor-mobilecron.git', :tag => s.version.to_s }
|
|
13
|
+
|
|
14
|
+
s.source_files = 'ios/Plugin/**/*.{swift,h,m}'
|
|
15
|
+
s.ios.deployment_target = '14.0'
|
|
16
|
+
|
|
17
|
+
s.dependency 'Capacitor'
|
|
18
|
+
s.swift_version = '5.9'
|
|
19
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Capacitor
|
|
3
|
+
import UIKit
|
|
3
4
|
|
|
4
5
|
@objc(MobileCronPlugin)
|
|
5
6
|
public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
@@ -14,29 +15,125 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
14
15
|
CAPPluginMethod(name: "pauseAll", returnType: CAPPluginReturnPromise),
|
|
15
16
|
CAPPluginMethod(name: "resumeAll", returnType: CAPPluginReturnPromise),
|
|
16
17
|
CAPPluginMethod(name: "setMode", returnType: CAPPluginReturnPromise),
|
|
17
|
-
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise)
|
|
18
|
+
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "__testNativeEvaluate", returnType: CAPPluginReturnPromise)
|
|
18
20
|
]
|
|
19
21
|
|
|
22
|
+
private static let storageKey = "mobilecron:state"
|
|
23
|
+
|
|
20
24
|
private var jobs: [String: [String: Any]] = [:]
|
|
21
25
|
private var paused = false
|
|
22
26
|
private(set) var mode = "balanced"
|
|
23
27
|
var currentMode: String { mode }
|
|
24
28
|
private var bgManager: BGTaskManager?
|
|
25
29
|
|
|
30
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
26
32
|
public override func load() {
|
|
27
33
|
super.load()
|
|
34
|
+
loadState()
|
|
35
|
+
firePendingNativeEvents()
|
|
28
36
|
let manager = BGTaskManager(plugin: self)
|
|
29
37
|
manager.registerBGTasks()
|
|
30
38
|
manager.scheduleRefresh()
|
|
31
39
|
manager.scheduleProcessing(requiresExternalPower: true)
|
|
32
40
|
self.bgManager = manager
|
|
41
|
+
NotificationCenter.default.addObserver(
|
|
42
|
+
self,
|
|
43
|
+
selector: #selector(appDidBecomeActive),
|
|
44
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
45
|
+
object: nil
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@objc private func appDidBecomeActive() {
|
|
50
|
+
firePendingNativeEvents()
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
func handleBackgroundWake(source: String) {
|
|
54
|
+
firePendingNativeEvents()
|
|
36
55
|
notifyListeners("statusChanged", data: buildStatus())
|
|
37
56
|
notifyListeners("nativeWake", data: ["source": source, "paused": paused])
|
|
38
57
|
}
|
|
39
58
|
|
|
59
|
+
// ── Persistence ───────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
private func loadState() {
|
|
62
|
+
guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
|
|
63
|
+
let data = raw.data(using: .utf8),
|
|
64
|
+
let state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
paused = (state["paused"] as? Bool) ?? false
|
|
68
|
+
mode = (state["mode"] as? String) ?? "balanced"
|
|
69
|
+
jobs = [:]
|
|
70
|
+
if let jobsArr = state["jobs"] as? [[String: Any]] {
|
|
71
|
+
for job in jobsArr {
|
|
72
|
+
if let id = job["id"] as? String, !id.isEmpty {
|
|
73
|
+
jobs[id] = job
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func saveState() {
|
|
80
|
+
var state: [String: Any] = [
|
|
81
|
+
"version": 1,
|
|
82
|
+
"paused": paused,
|
|
83
|
+
"mode": mode,
|
|
84
|
+
"jobs": Array(jobs.values)
|
|
85
|
+
]
|
|
86
|
+
// Preserve any pendingNativeEvents written by NativeJobEvaluator
|
|
87
|
+
if let raw = UserDefaults.standard.string(forKey: Self.storageKey),
|
|
88
|
+
let data = raw.data(using: .utf8),
|
|
89
|
+
let existing = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
|
|
90
|
+
let pending = existing["pendingNativeEvents"] as? [[String: Any]],
|
|
91
|
+
!pending.isEmpty {
|
|
92
|
+
state["pendingNativeEvents"] = pending
|
|
93
|
+
}
|
|
94
|
+
guard let data = try? JSONSerialization.data(withJSONObject: state),
|
|
95
|
+
let raw = String(data: data, encoding: .utf8) else { return }
|
|
96
|
+
UserDefaults.standard.set(raw, forKey: Self.storageKey)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Background wake ───────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/// Read pendingNativeEvents from storage, emit each as jobDue, then clear them.
|
|
102
|
+
private func firePendingNativeEvents() {
|
|
103
|
+
guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
|
|
104
|
+
let data = raw.data(using: .utf8),
|
|
105
|
+
var state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Sync native-evaluated job fields (nextDueAt, lastFiredAt, consecutiveSkips) into memory.
|
|
110
|
+
if let jobsArr = state["jobs"] as? [[String: Any]] {
|
|
111
|
+
for nativeJob in jobsArr {
|
|
112
|
+
if let id = nativeJob["id"] as? String, !id.isEmpty, jobs[id] != nil {
|
|
113
|
+
jobs[id] = nativeJob
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
guard let pending = state["pendingNativeEvents"] as? [[String: Any]], !pending.isEmpty else {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for evt in pending {
|
|
123
|
+
notifyListeners("jobDue", data: evt)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Clear pendingNativeEvents and write back updated jobs.
|
|
127
|
+
state.removeValue(forKey: "pendingNativeEvents")
|
|
128
|
+
state["jobs"] = Array(jobs.values)
|
|
129
|
+
if let newData = try? JSONSerialization.data(withJSONObject: state),
|
|
130
|
+
let newRaw = String(data: newData, encoding: .utf8) {
|
|
131
|
+
UserDefaults.standard.set(newRaw, forKey: Self.storageKey)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Plugin methods ────────────────────────────────────────────────────────
|
|
136
|
+
|
|
40
137
|
@objc func register(_ call: CAPPluginCall) {
|
|
41
138
|
guard let name = call.getString("name")?.trimmingCharacters(in: .whitespacesAndNewlines), !name.isEmpty else {
|
|
42
139
|
call.reject("Job name is required")
|
|
@@ -58,6 +155,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
58
155
|
if let data = call.getObject("data") { record["data"] = data }
|
|
59
156
|
|
|
60
157
|
jobs[id] = record
|
|
158
|
+
saveState()
|
|
61
159
|
notifyListeners("statusChanged", data: buildStatus())
|
|
62
160
|
call.resolve(["id": id])
|
|
63
161
|
}
|
|
@@ -68,6 +166,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
68
166
|
return
|
|
69
167
|
}
|
|
70
168
|
jobs.removeValue(forKey: id)
|
|
169
|
+
saveState()
|
|
71
170
|
notifyListeners("statusChanged", data: buildStatus())
|
|
72
171
|
call.resolve()
|
|
73
172
|
}
|
|
@@ -91,6 +190,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
91
190
|
if call.options.keys.contains("data") { existing["data"] = call.getObject("data") }
|
|
92
191
|
|
|
93
192
|
jobs[id] = existing
|
|
193
|
+
saveState()
|
|
94
194
|
notifyListeners("statusChanged", data: buildStatus())
|
|
95
195
|
call.resolve()
|
|
96
196
|
}
|
|
@@ -124,12 +224,14 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
124
224
|
|
|
125
225
|
@objc func pauseAll(_ call: CAPPluginCall) {
|
|
126
226
|
paused = true
|
|
227
|
+
saveState()
|
|
127
228
|
notifyListeners("statusChanged", data: buildStatus())
|
|
128
229
|
call.resolve()
|
|
129
230
|
}
|
|
130
231
|
|
|
131
232
|
@objc func resumeAll(_ call: CAPPluginCall) {
|
|
132
233
|
paused = false
|
|
234
|
+
saveState()
|
|
133
235
|
notifyListeners("statusChanged", data: buildStatus())
|
|
134
236
|
call.resolve()
|
|
135
237
|
}
|
|
@@ -144,6 +246,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
144
246
|
bgManager.scheduleRefresh()
|
|
145
247
|
bgManager.scheduleProcessing(requiresExternalPower: mode != "aggressive")
|
|
146
248
|
}
|
|
249
|
+
saveState()
|
|
147
250
|
notifyListeners("statusChanged", data: buildStatus())
|
|
148
251
|
call.resolve()
|
|
149
252
|
}
|
|
@@ -152,6 +255,14 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
152
255
|
call.resolve(buildStatus())
|
|
153
256
|
}
|
|
154
257
|
|
|
258
|
+
/// E2E test hook: directly calls NativeJobEvaluator.evaluate() and fires pending events.
|
|
259
|
+
/// Used to test native background evaluation without BGTaskScheduler.
|
|
260
|
+
@objc func __testNativeEvaluate(_ call: CAPPluginCall) {
|
|
261
|
+
let events = NativeJobEvaluator.evaluate(source: "test_trigger")
|
|
262
|
+
firePendingNativeEvents()
|
|
263
|
+
call.resolve(["firedCount": events.count])
|
|
264
|
+
}
|
|
265
|
+
|
|
155
266
|
private func buildStatus() -> [String: Any] {
|
|
156
267
|
let diagnostics = bgManager?.status ?? .init()
|
|
157
268
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-mobilecron",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Capacitor scheduling primitive that emits job due events across web, Android, and iOS",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"ios",
|
|
21
21
|
"dist",
|
|
22
22
|
"src",
|
|
23
|
+
"CapacitorMobilecron.podspec",
|
|
23
24
|
"package.json",
|
|
24
25
|
"README.md"
|
|
25
26
|
],
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"test:watch": "vitest",
|
|
60
61
|
"test:coverage": "vitest run --coverage",
|
|
61
62
|
"test:e2e": "node tests/e2e/test-e2e.mjs",
|
|
63
|
+
"test:e2e:ios": "node tests/e2e/test-e2e-ios.mjs",
|
|
62
64
|
"prepack": "npm run build"
|
|
63
65
|
},
|
|
64
66
|
"peerDependencies": {
|