capacitor-mobilecron 0.1.0
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.
- package/README.md +281 -0
- package/android/build.gradle +45 -0
- package/android/proguard-rules.pro +2 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/io/mobilecron/ChargingReceiver.kt +13 -0
- package/android/src/main/java/io/mobilecron/CronBridge.kt +20 -0
- package/android/src/main/java/io/mobilecron/CronChainWorker.kt +36 -0
- package/android/src/main/java/io/mobilecron/CronWorker.kt +15 -0
- package/android/src/main/java/io/mobilecron/MobileCronPlugin.kt +234 -0
- package/dist/.gitkeep +0 -0
- package/dist/esm/definitions.d.ts +96 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/mobilecron.d.ts +74 -0
- package/dist/esm/mobilecron.js +512 -0
- package/dist/esm/plugin.d.ts +2 -0
- package/dist/esm/plugin.js +4 -0
- package/dist/esm/web.d.ts +32 -0
- package/dist/esm/web.js +54 -0
- package/ios/Plugin/BGTaskManager.swift +78 -0
- package/ios/Plugin/MobileCronPlugin.m +13 -0
- package/ios/Plugin/MobileCronPlugin.swift +169 -0
- package/package.json +84 -0
- package/src/definitions.ts +99 -0
- package/src/index.ts +3 -0
- package/src/mobilecron.ts +598 -0
- package/src/plugin.ts +6 -0
- package/src/web.ts +81 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PluginListenerHandle, WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { CronJobOptions, CronStatus, JobDueEvent, JobSkippedEvent, MobileCronPlugin, OverdueEvent, SchedulingMode } from './definitions';
|
|
3
|
+
export declare class MobileCronWeb extends WebPlugin implements MobileCronPlugin {
|
|
4
|
+
private readonly scheduler;
|
|
5
|
+
private readonly ready;
|
|
6
|
+
constructor();
|
|
7
|
+
register(options: CronJobOptions): Promise<{
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
10
|
+
unregister(options: {
|
|
11
|
+
id: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
update(options: {
|
|
14
|
+
id: string;
|
|
15
|
+
} & Partial<CronJobOptions>): Promise<void>;
|
|
16
|
+
list(): Promise<{
|
|
17
|
+
jobs: import('./definitions').CronJobStatus[];
|
|
18
|
+
}>;
|
|
19
|
+
triggerNow(options: {
|
|
20
|
+
id: string;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
pauseAll(): Promise<void>;
|
|
23
|
+
resumeAll(): Promise<void>;
|
|
24
|
+
setMode(options: {
|
|
25
|
+
mode: SchedulingMode;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
getStatus(): Promise<CronStatus>;
|
|
28
|
+
addListener(event: 'jobDue', handler: (data: JobDueEvent) => void): Promise<PluginListenerHandle>;
|
|
29
|
+
addListener(event: 'jobSkipped', handler: (data: JobSkippedEvent) => void): Promise<PluginListenerHandle>;
|
|
30
|
+
addListener(event: 'overdueJobs', handler: (data: OverdueEvent) => void): Promise<PluginListenerHandle>;
|
|
31
|
+
addListener(event: 'statusChanged', handler: (data: CronStatus) => void): Promise<PluginListenerHandle>;
|
|
32
|
+
}
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import { MobileCronScheduler } from './mobilecron';
|
|
3
|
+
export class MobileCronWeb extends WebPlugin {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this.scheduler = new MobileCronScheduler({
|
|
7
|
+
platform: 'web',
|
|
8
|
+
onJobDue: (event) => this.notifyListeners('jobDue', event),
|
|
9
|
+
onJobSkipped: (event) => this.notifyListeners('jobSkipped', event),
|
|
10
|
+
onOverdue: (event) => this.notifyListeners('overdueJobs', event),
|
|
11
|
+
onStatusChanged: (status) => this.notifyListeners('statusChanged', status),
|
|
12
|
+
});
|
|
13
|
+
this.ready = this.scheduler.init();
|
|
14
|
+
}
|
|
15
|
+
async register(options) {
|
|
16
|
+
await this.ready;
|
|
17
|
+
return this.scheduler.register(options);
|
|
18
|
+
}
|
|
19
|
+
async unregister(options) {
|
|
20
|
+
await this.ready;
|
|
21
|
+
return this.scheduler.unregister(options);
|
|
22
|
+
}
|
|
23
|
+
async update(options) {
|
|
24
|
+
await this.ready;
|
|
25
|
+
return this.scheduler.update(options);
|
|
26
|
+
}
|
|
27
|
+
async list() {
|
|
28
|
+
await this.ready;
|
|
29
|
+
return this.scheduler.list();
|
|
30
|
+
}
|
|
31
|
+
async triggerNow(options) {
|
|
32
|
+
await this.ready;
|
|
33
|
+
return this.scheduler.triggerNow(options);
|
|
34
|
+
}
|
|
35
|
+
async pauseAll() {
|
|
36
|
+
await this.ready;
|
|
37
|
+
return this.scheduler.pauseAll();
|
|
38
|
+
}
|
|
39
|
+
async resumeAll() {
|
|
40
|
+
await this.ready;
|
|
41
|
+
return this.scheduler.resumeAll();
|
|
42
|
+
}
|
|
43
|
+
async setMode(options) {
|
|
44
|
+
await this.ready;
|
|
45
|
+
return this.scheduler.setMode(options.mode);
|
|
46
|
+
}
|
|
47
|
+
async getStatus() {
|
|
48
|
+
await this.ready;
|
|
49
|
+
return this.scheduler.getStatus();
|
|
50
|
+
}
|
|
51
|
+
async addListener(eventName, listenerFunc) {
|
|
52
|
+
return super.addListener(eventName, listenerFunc);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import BackgroundTasks
|
|
3
|
+
|
|
4
|
+
final class BGTaskManager {
|
|
5
|
+
struct Status {
|
|
6
|
+
var bgRefreshRegistered: Bool = false
|
|
7
|
+
var bgProcessingRegistered: Bool = false
|
|
8
|
+
var bgContinuedAvailable: Bool = false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private weak var plugin: MobileCronPlugin?
|
|
12
|
+
private(set) var status = Status()
|
|
13
|
+
|
|
14
|
+
init(plugin: MobileCronPlugin) {
|
|
15
|
+
self.plugin = plugin
|
|
16
|
+
self.status.bgContinuedAvailable = false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func registerBGTasks() {
|
|
20
|
+
status.bgRefreshRegistered = BGTaskScheduler.shared.register(
|
|
21
|
+
forTaskWithIdentifier: "io.mobilecron.refresh",
|
|
22
|
+
using: nil
|
|
23
|
+
) { [weak self] task in
|
|
24
|
+
guard let refreshTask = task as? BGAppRefreshTask else {
|
|
25
|
+
task.setTaskCompleted(success: false)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
self?.handleRefresh(refreshTask)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
status.bgProcessingRegistered = BGTaskScheduler.shared.register(
|
|
32
|
+
forTaskWithIdentifier: "io.mobilecron.processing",
|
|
33
|
+
using: nil
|
|
34
|
+
) { [weak self] task in
|
|
35
|
+
guard let processingTask = task as? BGProcessingTask else {
|
|
36
|
+
task.setTaskCompleted(success: false)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
self?.handleProcessing(processingTask)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// iOS 26+ BGContinuedProcessingTask placeholder: keep runtime feature flag separate from compile-time SDK use.
|
|
43
|
+
status.bgContinuedAvailable = false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func scheduleRefresh() {
|
|
47
|
+
let request = BGAppRefreshTaskRequest(identifier: "io.mobilecron.refresh")
|
|
48
|
+
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
|
49
|
+
try? BGTaskScheduler.shared.submit(request)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func scheduleProcessing(requiresExternalPower: Bool) {
|
|
53
|
+
let request = BGProcessingTaskRequest(identifier: "io.mobilecron.processing")
|
|
54
|
+
request.requiresExternalPower = requiresExternalPower
|
|
55
|
+
request.requiresNetworkConnectivity = false
|
|
56
|
+
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
|
57
|
+
try? BGTaskScheduler.shared.submit(request)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private func handleRefresh(_ task: BGAppRefreshTask) {
|
|
61
|
+
scheduleRefresh()
|
|
62
|
+
task.expirationHandler = {
|
|
63
|
+
task.setTaskCompleted(success: false)
|
|
64
|
+
}
|
|
65
|
+
plugin?.handleBackgroundWake(source: "bgtask_refresh")
|
|
66
|
+
task.setTaskCompleted(success: true)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private func handleProcessing(_ task: BGProcessingTask) {
|
|
70
|
+
let mode = plugin?.currentMode ?? "balanced"
|
|
71
|
+
scheduleProcessing(requiresExternalPower: mode != "aggressive")
|
|
72
|
+
task.expirationHandler = {
|
|
73
|
+
task.setTaskCompleted(success: false)
|
|
74
|
+
}
|
|
75
|
+
plugin?.handleBackgroundWake(source: "bgtask_processing")
|
|
76
|
+
task.setTaskCompleted(success: true)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import <Capacitor/Capacitor.h>
|
|
2
|
+
|
|
3
|
+
CAP_PLUGIN(MobileCronPlugin, "MobileCron",
|
|
4
|
+
CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise);
|
|
5
|
+
CAP_PLUGIN_METHOD(unregister, CAPPluginReturnPromise);
|
|
6
|
+
CAP_PLUGIN_METHOD(update, CAPPluginReturnPromise);
|
|
7
|
+
CAP_PLUGIN_METHOD(list, CAPPluginReturnPromise);
|
|
8
|
+
CAP_PLUGIN_METHOD(triggerNow, CAPPluginReturnPromise);
|
|
9
|
+
CAP_PLUGIN_METHOD(pauseAll, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(resumeAll, CAPPluginReturnPromise);
|
|
11
|
+
CAP_PLUGIN_METHOD(setMode, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(getStatus, CAPPluginReturnPromise);
|
|
13
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
@objc(MobileCronPlugin)
|
|
5
|
+
public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
+
public let identifier = "MobileCronPlugin"
|
|
7
|
+
public let jsName = "MobileCron"
|
|
8
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
9
|
+
CAPPluginMethod(name: "register", returnType: CAPPluginReturnPromise),
|
|
10
|
+
CAPPluginMethod(name: "unregister", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "update", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "list", returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: "triggerNow", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "pauseAll", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "resumeAll", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "setMode", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise)
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
private var jobs: [String: [String: Any]] = [:]
|
|
21
|
+
private var paused = false
|
|
22
|
+
private(set) var mode = "balanced"
|
|
23
|
+
var currentMode: String { mode }
|
|
24
|
+
private var bgManager: BGTaskManager?
|
|
25
|
+
|
|
26
|
+
public override func load() {
|
|
27
|
+
super.load()
|
|
28
|
+
let manager = BGTaskManager(plugin: self)
|
|
29
|
+
manager.registerBGTasks()
|
|
30
|
+
manager.scheduleRefresh()
|
|
31
|
+
manager.scheduleProcessing(requiresExternalPower: true)
|
|
32
|
+
self.bgManager = manager
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func handleBackgroundWake(source: String) {
|
|
36
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
37
|
+
notifyListeners("nativeWake", data: ["source": source, "paused": paused])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@objc func register(_ call: CAPPluginCall) {
|
|
41
|
+
guard let name = call.getString("name")?.trimmingCharacters(in: .whitespacesAndNewlines), !name.isEmpty else {
|
|
42
|
+
call.reject("Job name is required")
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let id = UUID().uuidString
|
|
47
|
+
var record: [String: Any] = [
|
|
48
|
+
"id": id,
|
|
49
|
+
"name": name,
|
|
50
|
+
"enabled": true,
|
|
51
|
+
"consecutiveSkips": 0
|
|
52
|
+
]
|
|
53
|
+
if let schedule = call.getObject("schedule") { record["schedule"] = schedule }
|
|
54
|
+
if let activeHours = call.getObject("activeHours") { record["activeHours"] = activeHours }
|
|
55
|
+
if call.options.keys.contains("requiresNetwork") { record["requiresNetwork"] = call.getBool("requiresNetwork") ?? false }
|
|
56
|
+
if call.options.keys.contains("requiresCharging") { record["requiresCharging"] = call.getBool("requiresCharging") ?? false }
|
|
57
|
+
if let priority = call.getString("priority") { record["priority"] = priority }
|
|
58
|
+
if let data = call.getObject("data") { record["data"] = data }
|
|
59
|
+
|
|
60
|
+
jobs[id] = record
|
|
61
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
62
|
+
call.resolve(["id": id])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@objc func unregister(_ call: CAPPluginCall) {
|
|
66
|
+
guard let id = call.getString("id") else {
|
|
67
|
+
call.reject("id is required")
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
jobs.removeValue(forKey: id)
|
|
71
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
72
|
+
call.resolve()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc func update(_ call: CAPPluginCall) {
|
|
76
|
+
guard let id = call.getString("id") else {
|
|
77
|
+
call.reject("id is required")
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
guard var existing = jobs[id] else {
|
|
81
|
+
call.reject("Job not found")
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if let name = call.getString("name") { existing["name"] = name }
|
|
86
|
+
if let schedule = call.getObject("schedule") { existing["schedule"] = schedule }
|
|
87
|
+
if call.options.keys.contains("activeHours") { existing["activeHours"] = call.getObject("activeHours") }
|
|
88
|
+
if call.options.keys.contains("requiresNetwork") { existing["requiresNetwork"] = call.getBool("requiresNetwork") ?? false }
|
|
89
|
+
if call.options.keys.contains("requiresCharging") { existing["requiresCharging"] = call.getBool("requiresCharging") ?? false }
|
|
90
|
+
if let priority = call.getString("priority") { existing["priority"] = priority }
|
|
91
|
+
if call.options.keys.contains("data") { existing["data"] = call.getObject("data") }
|
|
92
|
+
|
|
93
|
+
jobs[id] = existing
|
|
94
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
95
|
+
call.resolve()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@objc func list(_ call: CAPPluginCall) {
|
|
99
|
+
call.resolve(["jobs": Array(jobs.values)])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@objc func triggerNow(_ call: CAPPluginCall) {
|
|
103
|
+
guard let id = call.getString("id") else {
|
|
104
|
+
call.reject("id is required")
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
guard let job = jobs[id] else {
|
|
108
|
+
call.reject("Job not found")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
var payload: [String: Any] = [
|
|
113
|
+
"id": id,
|
|
114
|
+
"name": (job["name"] as? String) ?? "",
|
|
115
|
+
"firedAt": Int(Date().timeIntervalSince1970 * 1000),
|
|
116
|
+
"source": "manual"
|
|
117
|
+
]
|
|
118
|
+
if let data = job["data"] {
|
|
119
|
+
payload["data"] = data
|
|
120
|
+
}
|
|
121
|
+
notifyListeners("jobDue", data: payload)
|
|
122
|
+
call.resolve()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@objc func pauseAll(_ call: CAPPluginCall) {
|
|
126
|
+
paused = true
|
|
127
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
128
|
+
call.resolve()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc func resumeAll(_ call: CAPPluginCall) {
|
|
132
|
+
paused = false
|
|
133
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
134
|
+
call.resolve()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc func setMode(_ call: CAPPluginCall) {
|
|
138
|
+
guard let mode = call.getString("mode"), ["eco", "balanced", "aggressive"].contains(mode) else {
|
|
139
|
+
call.reject("mode must be eco|balanced|aggressive")
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
self.mode = mode
|
|
143
|
+
if let bgManager {
|
|
144
|
+
bgManager.scheduleRefresh()
|
|
145
|
+
bgManager.scheduleProcessing(requiresExternalPower: mode != "aggressive")
|
|
146
|
+
}
|
|
147
|
+
notifyListeners("statusChanged", data: buildStatus())
|
|
148
|
+
call.resolve()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@objc func getStatus(_ call: CAPPluginCall) {
|
|
152
|
+
call.resolve(buildStatus())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private func buildStatus() -> [String: Any] {
|
|
156
|
+
let diagnostics = bgManager?.status ?? .init()
|
|
157
|
+
return [
|
|
158
|
+
"paused": paused,
|
|
159
|
+
"mode": mode,
|
|
160
|
+
"platform": "ios",
|
|
161
|
+
"activeJobCount": jobs.count,
|
|
162
|
+
"ios": [
|
|
163
|
+
"bgRefreshRegistered": diagnostics.bgRefreshRegistered,
|
|
164
|
+
"bgProcessingRegistered": diagnostics.bgProcessingRegistered,
|
|
165
|
+
"bgContinuedAvailable": diagnostics.bgContinuedAvailable
|
|
166
|
+
]
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-mobilecron",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Capacitor scheduling primitive that emits job due events across web, Android, and iOS",
|
|
5
|
+
"main": "dist/esm/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"capacitor": {
|
|
9
|
+
"android": {
|
|
10
|
+
"src": "android"
|
|
11
|
+
},
|
|
12
|
+
"ios": {
|
|
13
|
+
"src": "ios"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"android/src",
|
|
18
|
+
"android/build.gradle",
|
|
19
|
+
"android/proguard-rules.pro",
|
|
20
|
+
"ios",
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"package.json",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"author": "Rogelio Ruiz",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/rogelioRuiz/capacitor-mobilecron.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/rogelioRuiz/capacitor-mobilecron/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/rogelioRuiz/capacitor-mobilecron#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"capacitor",
|
|
38
|
+
"plugin",
|
|
39
|
+
"scheduler",
|
|
40
|
+
"cron",
|
|
41
|
+
"mobile",
|
|
42
|
+
"background",
|
|
43
|
+
"jobs",
|
|
44
|
+
"android",
|
|
45
|
+
"ios"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.json",
|
|
52
|
+
"build:watch": "tsc -p tsconfig.json --watch",
|
|
53
|
+
"clean": "rm -rf dist",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"lint": "npx biome check .",
|
|
56
|
+
"lint:fix": "npx biome check --write .",
|
|
57
|
+
"format": "npx biome format --write .",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:coverage": "vitest run --coverage",
|
|
61
|
+
"test:e2e": "node tests/e2e/test-e2e.mjs",
|
|
62
|
+
"prepack": "npm run build"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@capacitor/core": ">=6.0.0",
|
|
66
|
+
"@capacitor/preferences": ">=6.0.0",
|
|
67
|
+
"@capacitor/app": ">=6.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"@capacitor/app": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@biomejs/biome": "^1.9.4",
|
|
76
|
+
"@capacitor/app": "^6.0.0",
|
|
77
|
+
"@capacitor/core": "^6.0.0",
|
|
78
|
+
"@capacitor/preferences": "^6.0.0",
|
|
79
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
80
|
+
"happy-dom": "^17.5.5",
|
|
81
|
+
"typescript": "^5.6.3",
|
|
82
|
+
"vitest": "^3.2.4"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { PluginListenerHandle } from '@capacitor/core'
|
|
2
|
+
|
|
3
|
+
export interface MobileCronPlugin {
|
|
4
|
+
register(options: CronJobOptions): Promise<{ id: string }>
|
|
5
|
+
unregister(options: { id: string }): Promise<void>
|
|
6
|
+
update(options: { id: string } & Partial<CronJobOptions>): Promise<void>
|
|
7
|
+
list(): Promise<{ jobs: CronJobStatus[] }>
|
|
8
|
+
triggerNow(options: { id: string }): Promise<void>
|
|
9
|
+
|
|
10
|
+
pauseAll(): Promise<void>
|
|
11
|
+
resumeAll(): Promise<void>
|
|
12
|
+
setMode(options: { mode: SchedulingMode }): Promise<void>
|
|
13
|
+
getStatus(): Promise<CronStatus>
|
|
14
|
+
|
|
15
|
+
addListener(event: 'jobDue', handler: (data: JobDueEvent) => void): Promise<PluginListenerHandle>
|
|
16
|
+
addListener(event: 'jobSkipped', handler: (data: JobSkippedEvent) => void): Promise<PluginListenerHandle>
|
|
17
|
+
addListener(event: 'overdueJobs', handler: (data: OverdueEvent) => void): Promise<PluginListenerHandle>
|
|
18
|
+
addListener(event: 'statusChanged', handler: (data: CronStatus) => void): Promise<PluginListenerHandle>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CronJobOptions {
|
|
22
|
+
name: string
|
|
23
|
+
schedule: CronSchedule
|
|
24
|
+
activeHours?: ActiveHours
|
|
25
|
+
requiresNetwork?: boolean
|
|
26
|
+
requiresCharging?: boolean
|
|
27
|
+
priority?: 'low' | 'normal' | 'high'
|
|
28
|
+
data?: Record<string, unknown>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CronSchedule {
|
|
32
|
+
kind: 'every' | 'at'
|
|
33
|
+
everyMs?: number
|
|
34
|
+
anchorMs?: number
|
|
35
|
+
atMs?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ActiveHours {
|
|
39
|
+
start: string
|
|
40
|
+
end: string
|
|
41
|
+
tz?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SchedulingMode = 'eco' | 'balanced' | 'aggressive'
|
|
45
|
+
|
|
46
|
+
export interface CronJobStatus {
|
|
47
|
+
id: string
|
|
48
|
+
name: string
|
|
49
|
+
enabled: boolean
|
|
50
|
+
schedule: CronSchedule
|
|
51
|
+
lastFiredAt?: number
|
|
52
|
+
nextDueAt?: number
|
|
53
|
+
consecutiveSkips: number
|
|
54
|
+
data?: Record<string, unknown>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CronStatus {
|
|
58
|
+
paused: boolean
|
|
59
|
+
mode: SchedulingMode
|
|
60
|
+
platform: 'android' | 'ios' | 'web'
|
|
61
|
+
activeJobCount: number
|
|
62
|
+
nextDueAt?: number
|
|
63
|
+
android?: { workManagerActive: boolean; chargingReceiverActive: boolean }
|
|
64
|
+
ios?: {
|
|
65
|
+
bgRefreshRegistered: boolean
|
|
66
|
+
bgProcessingRegistered: boolean
|
|
67
|
+
bgContinuedAvailable: boolean
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface JobDueEvent {
|
|
72
|
+
id: string
|
|
73
|
+
name: string
|
|
74
|
+
firedAt: number
|
|
75
|
+
source: WakeSource
|
|
76
|
+
data?: Record<string, unknown>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type WakeSource =
|
|
80
|
+
| 'watchdog'
|
|
81
|
+
| 'workmanager'
|
|
82
|
+
| 'workmanager_chain'
|
|
83
|
+
| 'charging'
|
|
84
|
+
| 'foreground'
|
|
85
|
+
| 'bgtask_refresh'
|
|
86
|
+
| 'bgtask_processing'
|
|
87
|
+
| 'bgtask_continued'
|
|
88
|
+
| 'manual'
|
|
89
|
+
|
|
90
|
+
export interface JobSkippedEvent {
|
|
91
|
+
id: string
|
|
92
|
+
name: string
|
|
93
|
+
reason: 'outside_active_hours' | 'paused' | 'requires_network' | 'requires_charging'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface OverdueEvent {
|
|
97
|
+
count: number
|
|
98
|
+
jobs: Array<{ id: string; name: string; overdueMs: number }>
|
|
99
|
+
}
|
package/src/index.ts
ADDED