capacitor-mobilecron 0.2.4 → 0.2.6

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.
@@ -22,8 +22,6 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
22
22
  CAPPluginMethod(name: "testGetPendingCount", returnType: CAPPluginReturnPromise)
23
23
  ]
24
24
 
25
- private static let storageKey = "mobilecron:state"
26
-
27
25
  private var jobs: [String: [String: Any]] = [:]
28
26
  private var paused = false
29
27
  private(set) var mode = "balanced"
@@ -60,9 +58,11 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
60
58
  }
61
59
 
62
60
  // ── Persistence ───────────────────────────────────────────────────────────
61
+ // Uses NativeJobEvaluator.readStateRaw() / writeStateRaw() for file-backed
62
+ // atomic writes that survive simctl terminate (SIGKILL) reliably.
63
63
 
64
64
  private func loadState() {
65
- guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
65
+ guard let raw = NativeJobEvaluator.readStateRaw(),
66
66
  let data = raw.data(using: .utf8),
67
67
  let state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
68
68
  return
@@ -87,7 +87,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
87
87
  "jobs": Array(jobs.values)
88
88
  ]
89
89
  // Preserve any pendingNativeEvents written by NativeJobEvaluator
90
- if let raw = UserDefaults.standard.string(forKey: Self.storageKey),
90
+ if let raw = NativeJobEvaluator.readStateRaw(),
91
91
  let data = raw.data(using: .utf8),
92
92
  let existing = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
93
93
  let pending = existing["pendingNativeEvents"] as? [[String: Any]],
@@ -96,16 +96,14 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
96
96
  }
97
97
  guard let data = try? JSONSerialization.data(withJSONObject: state),
98
98
  let raw = String(data: data, encoding: .utf8) else { return }
99
- UserDefaults.standard.set(raw, forKey: Self.storageKey)
100
- // Force immediate disk flush so state survives simctl terminate / force-kill
101
- UserDefaults.standard.synchronize()
99
+ NativeJobEvaluator.writeStateRaw(raw)
102
100
  }
103
101
 
104
102
  // ── Background wake ───────────────────────────────────────────────────────
105
103
 
106
104
  /// Read pendingNativeEvents from storage, emit each as jobDue, then clear them.
107
105
  private func firePendingNativeEvents() {
108
- guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
106
+ guard let raw = NativeJobEvaluator.readStateRaw(),
109
107
  let data = raw.data(using: .utf8),
110
108
  var state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
111
109
  return
@@ -133,8 +131,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
133
131
  state["jobs"] = Array(jobs.values)
134
132
  if let newData = try? JSONSerialization.data(withJSONObject: state),
135
133
  let newRaw = String(data: newData, encoding: .utf8) {
136
- UserDefaults.standard.set(newRaw, forKey: Self.storageKey)
137
- UserDefaults.standard.synchronize()
134
+ NativeJobEvaluator.writeStateRaw(newRaw)
138
135
  }
139
136
  }
140
137
 
@@ -270,8 +267,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
270
267
  call.resolve(["firedCount": events.count])
271
268
  }
272
269
 
273
- /// Sets a job's nextDueAt in memory and saves to UserDefaults.
274
- /// Allows tests to mark a job as "due" without waiting for real time to pass.
270
+ /// Sets a job's nextDueAt in memory and saves to storage.
275
271
  @objc func testSetNextDueAt(_ call: CAPPluginCall) {
276
272
  guard let id = call.getString("id"),
277
273
  let nextDueAtMs = call.getInt("nextDueAtMs"),
@@ -284,17 +280,16 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
284
280
  call.resolve()
285
281
  }
286
282
 
287
- /// Appends a pending native event to UserDefaults storage.
288
- /// Allows tests to simulate what NativeJobEvaluator writes during background execution.
283
+ /// Appends a pending native event to storage.
289
284
  @objc func testInjectPendingEvent(_ call: CAPPluginCall) {
290
285
  guard let event = call.getObject("event") else {
291
286
  call.reject("event is required")
292
287
  return
293
288
  }
294
- guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
289
+ guard let raw = NativeJobEvaluator.readStateRaw(),
295
290
  let data = raw.data(using: .utf8),
296
291
  var state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
297
- call.reject("State not found in UserDefaults")
292
+ call.reject("State not found in storage")
298
293
  return
299
294
  }
300
295
  var pending = (state["pendingNativeEvents"] as? [[String: Any]]) ?? []
@@ -305,14 +300,13 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
305
300
  call.reject("Serialization failed")
306
301
  return
307
302
  }
308
- UserDefaults.standard.set(newRaw, forKey: Self.storageKey)
309
- UserDefaults.standard.synchronize()
303
+ NativeJobEvaluator.writeStateRaw(newRaw)
310
304
  call.resolve()
311
305
  }
312
306
 
313
- /// Returns the count of pendingNativeEvents currently in UserDefaults storage.
307
+ /// Returns the count of pendingNativeEvents currently in storage.
314
308
  @objc func testGetPendingCount(_ call: CAPPluginCall) {
315
- guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
309
+ guard let raw = NativeJobEvaluator.readStateRaw(),
316
310
  let data = raw.data(using: .utf8),
317
311
  let state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
318
312
  call.resolve(["count": 0])
@@ -12,8 +12,41 @@ final class NativeJobEvaluator {
12
12
  private static let storageKey = "mobilecron:state"
13
13
  private static let clockRegex = try? NSRegularExpression(pattern: #"^(\d{2}):(\d{2})$"#)
14
14
 
15
+ // ── Shared I/O ─────────────────────────────────────────────────────────────
16
+
17
+ /// JSON file in Application Support — survives simctl terminate (synchronous atomic write).
18
+ static var stateFileURL: URL? {
19
+ guard let dir = try? FileManager.default.url(
20
+ for: .applicationSupportDirectory,
21
+ in: .userDomainMask,
22
+ appropriateFor: nil,
23
+ create: true
24
+ ) else { return nil }
25
+ return dir.appendingPathComponent("mobilecron-state.json")
26
+ }
27
+
28
+ /// Reads raw JSON: file preferred (reliable); UserDefaults as fallback.
29
+ static func readStateRaw() -> String? {
30
+ if let url = stateFileURL,
31
+ let raw = try? String(contentsOf: url, encoding: .utf8),
32
+ !raw.isEmpty {
33
+ return raw
34
+ }
35
+ return UserDefaults.standard.string(forKey: storageKey)
36
+ }
37
+
38
+ /// Writes raw JSON atomically to file AND to UserDefaults (belt-and-suspenders).
39
+ static func writeStateRaw(_ raw: String) {
40
+ if let url = stateFileURL {
41
+ try? raw.write(to: url, atomically: true, encoding: .utf8)
42
+ }
43
+ UserDefaults.standard.set(raw, forKey: storageKey)
44
+ }
45
+
46
+ // ── Evaluation ─────────────────────────────────────────────────────────────
47
+
15
48
  static func evaluate(source: String) -> [NativeJobEvent] {
16
- guard let raw = UserDefaults.standard.string(forKey: storageKey),
49
+ guard let raw = readStateRaw(),
17
50
  let data = raw.data(using: .utf8),
18
51
  var state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
19
52
  return []
@@ -101,8 +134,8 @@ final class NativeJobEvaluator {
101
134
  }
102
135
 
103
136
  if let nextData = try? JSONSerialization.data(withJSONObject: state),
104
- let nextRaw = String(data: nextData, encoding: .utf8) {
105
- UserDefaults.standard.set(nextRaw, forKey: storageKey)
137
+ let nextRaw = String(nextData, encoding: .utf8) {
138
+ writeStateRaw(nextRaw)
106
139
  }
107
140
  }
108
141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-mobilecron",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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",