@capacitor/background-runner 1.0.6-dev-20230831T174057.0 → 1.0.6-dev-20240409T144959.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 +3 -2
- package/android/build.gradle +4 -1
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt +67 -51
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +30 -14
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerConfig.kt +28 -23
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerWorker.kt +8 -2
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +1 -2
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Geolocation.kt +1 -1
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/KV.kt +1 -1
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +4 -2
- package/android/src/main/libs/android-js-engine-release.aar +0 -0
- package/dist/docs-api.json +1 -1
- package/dist/docs.json +3 -2
- package/dist/esm/apis.d.ts +1 -1
- package/dist/esm/apis.js.map +1 -1
- package/dist/esm/definitions.d.ts +1 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js +1 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +1 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/BackgroundRunner.swift +13 -52
- package/ios/Plugin/BackgroundRunnerPlugin.swift +2 -1
- package/ios/Plugin/Context+CapacitorAPI.swift +5 -5
- package/ios/Plugin/RunnerEngine/Context.swift +27 -44
- package/ios/Plugin/RunnerEngine/Errors.swift +1 -0
- package/ios/Plugin/RunnerEngine/Runner.swift +43 -3
- package/package.json +3 -3
- package/scripts/install_libs.js +33 -11
package/README.md
CHANGED
|
@@ -314,7 +314,7 @@ Request permission to display local notifications.
|
|
|
314
314
|
### dispatchEvent(...)
|
|
315
315
|
|
|
316
316
|
```typescript
|
|
317
|
-
dispatchEvent(options: DispatchEventOptions) => any
|
|
317
|
+
dispatchEvent<T = void>(options: DispatchEventOptions) => any
|
|
318
318
|
```
|
|
319
319
|
|
|
320
320
|
Dispatches an event to the configured runner.
|
|
@@ -456,7 +456,7 @@ Get access to device location information.
|
|
|
456
456
|
|
|
457
457
|
| Prop | Type | Description | Since |
|
|
458
458
|
| ------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------ | ----- |
|
|
459
|
-
| **`
|
|
459
|
+
| **`getCurrentPosition`** | <code>() => <a href="#getcurrentpositionresult">GetCurrentPositionResult</a></code> | Get the device's last known location | 1.0.0 |
|
|
460
460
|
|
|
461
461
|
|
|
462
462
|
#### GetCurrentPositionResult
|
|
@@ -488,4 +488,5 @@ They could be used if a native watch app is developed as a companion app to a Ca
|
|
|
488
488
|
| **`updateWatchUI`** | <code>(options: { watchUI: string; }) => void</code> | Replaces the current UI on the watch with what is specified here. |
|
|
489
489
|
| **`updateWatchData`** | <code>(options: { data: { [key: string]: string; }; }) => void</code> | Updates the data the watch is using to display variables in text and button fields |
|
|
490
490
|
|
|
491
|
+
|
|
491
492
|
</capacitor-api-docs>
|
package/android/build.gradle
CHANGED
|
@@ -52,7 +52,10 @@ android {
|
|
|
52
52
|
repositories {
|
|
53
53
|
google()
|
|
54
54
|
mavenCentral()
|
|
55
|
-
flatDir {
|
|
55
|
+
flatDir {
|
|
56
|
+
dirs "./src/main/libs", "libs"
|
|
57
|
+
dirs "$rootDir/../node_modules/@capacitor/background-runner/android/src/main/libs", "libs"
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
dependencies {
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
4
4
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
|
5
5
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
6
7
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
8
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
7
9
|
|
|
8
10
|
<application>
|
|
9
11
|
<receiver android:name="io.ionic.backgroundrunner.plugin.api.TimedNotificationPublisher" />
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
package io.ionic.backgroundrunner.plugin
|
|
2
2
|
|
|
3
3
|
import android.content.res.AssetManager
|
|
4
|
+
import androidx.work.Constraints
|
|
4
5
|
import androidx.work.Data
|
|
5
6
|
import androidx.work.ExistingPeriodicWorkPolicy
|
|
6
7
|
import androidx.work.ExistingWorkPolicy
|
|
8
|
+
import androidx.work.NetworkType
|
|
7
9
|
import androidx.work.OneTimeWorkRequest
|
|
8
10
|
import androidx.work.PeriodicWorkRequest
|
|
9
11
|
import androidx.work.WorkManager
|
|
10
12
|
import io.ionic.android_js_engine.Context
|
|
11
|
-
import io.ionic.android_js_engine.
|
|
13
|
+
import io.ionic.android_js_engine.NativeCapacitorAPI
|
|
14
|
+
import io.ionic.android_js_engine.NativeJSFunction
|
|
12
15
|
import io.ionic.android_js_engine.Runner
|
|
13
|
-
import io.ionic.android_js_engine.api.CapacitorAPI
|
|
14
16
|
import io.ionic.backgroundrunner.plugin.api.Device
|
|
15
17
|
import io.ionic.backgroundrunner.plugin.api.Geolocation
|
|
16
18
|
import io.ionic.backgroundrunner.plugin.api.KV
|
|
@@ -24,33 +26,23 @@ import java.util.concurrent.TimeUnit
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class BackgroundRunner(context: android.content.Context) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private var context: Context? = null
|
|
30
|
-
|
|
31
|
-
companion object {
|
|
32
|
-
@Volatile private var instance: BackgroundRunner? = null
|
|
33
|
-
|
|
34
|
-
fun getInstance(context: android.content.Context): BackgroundRunner = instance ?: synchronized(this) {
|
|
35
|
-
instance ?: BackgroundRunner(context).also { instance = it }
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private val started: Boolean
|
|
40
|
-
get() {
|
|
41
|
-
return runner != null && context != null
|
|
42
|
-
}
|
|
29
|
+
private var runner: Runner?
|
|
30
|
+
val config: RunnerConfig?
|
|
43
31
|
|
|
44
32
|
init {
|
|
45
33
|
config = loadRunnerConfig(context.assets)
|
|
34
|
+
runner = null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun start() {
|
|
38
|
+
this.runner = Runner()
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
fun
|
|
49
|
-
|
|
41
|
+
fun shutdown() {
|
|
42
|
+
val runner = runner ?: return
|
|
50
43
|
|
|
51
|
-
runner
|
|
52
|
-
|
|
53
|
-
context?.start()
|
|
44
|
+
runner.destroy()
|
|
45
|
+
this.runner = null
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
fun scheduleBackgroundTask(androidContext: android.content.Context) {
|
|
@@ -68,11 +60,16 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
68
60
|
.putString("event", config.event)
|
|
69
61
|
.build()
|
|
70
62
|
|
|
63
|
+
val constraints = Constraints.Builder()
|
|
64
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
65
|
+
.build()
|
|
66
|
+
|
|
71
67
|
if (!config.repeats) {
|
|
72
68
|
val work = OneTimeWorkRequest.Builder(RunnerWorker::class.java)
|
|
73
69
|
.setInitialDelay(interval.toLong(), TimeUnit.MINUTES)
|
|
74
70
|
.setInputData(data)
|
|
75
71
|
.addTag(config.label)
|
|
72
|
+
.setConstraints(constraints)
|
|
76
73
|
.build()
|
|
77
74
|
WorkManager.getInstance(androidContext).enqueueUniqueWork(config.label, ExistingWorkPolicy.REPLACE, work)
|
|
78
75
|
} else {
|
|
@@ -80,42 +77,53 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
80
77
|
.setInitialDelay(interval.toLong(), TimeUnit.MINUTES)
|
|
81
78
|
.setInputData(data)
|
|
82
79
|
.addTag(config.label)
|
|
80
|
+
.setConstraints(constraints)
|
|
83
81
|
.build()
|
|
84
82
|
WorkManager.getInstance(androidContext).enqueueUniquePeriodicWork(config.label, ExistingPeriodicWorkPolicy.UPDATE, work)
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
suspend fun execute(androidContext: android.content.Context, config: RunnerConfig, dataArgs: JSONObject = JSONObject()): JSONObject? {
|
|
89
|
-
|
|
90
|
-
start(androidContext)
|
|
91
|
-
}
|
|
86
|
+
suspend fun execute(androidContext: android.content.Context, config: RunnerConfig, dataArgs: JSONObject = JSONObject(), callbackId: String? = null): JSONObject? {
|
|
87
|
+
val runner = runner ?: throw Exception("runner is not started")
|
|
92
88
|
|
|
93
|
-
context
|
|
89
|
+
val context = initContext(config, androidContext, callbackId)
|
|
94
90
|
|
|
95
|
-
|
|
91
|
+
class ResolveCallback(future: MutableStateFlow<Result<JSONObject?>?>) : NativeJSFunction(jsFunctionName = "resolve") {
|
|
92
|
+
var future: MutableStateFlow<Result<JSONObject?>?>
|
|
93
|
+
|
|
94
|
+
init {
|
|
95
|
+
this.future = future
|
|
96
|
+
}
|
|
96
97
|
|
|
97
|
-
class ResolveCallback : JSFunction(jsName = "resolve") {
|
|
98
98
|
override fun run() {
|
|
99
99
|
super.run()
|
|
100
|
-
future.value = Result.success(this.
|
|
100
|
+
future.value = Result.success(this.jsFunctionArgs)
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
class RejectCallback :
|
|
104
|
+
class RejectCallback(future: MutableStateFlow<Result<JSONObject?>?>) : NativeJSFunction(jsFunctionName = "reject") {
|
|
105
|
+
var future: MutableStateFlow<Result<JSONObject?>?>
|
|
106
|
+
|
|
107
|
+
init {
|
|
108
|
+
this.future = future
|
|
109
|
+
}
|
|
110
|
+
|
|
105
111
|
override fun run() {
|
|
106
112
|
super.run()
|
|
107
|
-
val rejectionTitle = this.
|
|
108
|
-
val rejectionMessage = this.
|
|
113
|
+
val rejectionTitle = this.jsFunctionArgs?.optString("name", "Error") ?: "Error"
|
|
114
|
+
val rejectionMessage = this.jsFunctionArgs?.optString("message") ?: ""
|
|
109
115
|
val error = Exception("$rejectionTitle: $rejectionMessage")
|
|
110
116
|
|
|
111
117
|
future.value = Result.failure(error)
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
val
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
val future = MutableStateFlow<Result<JSONObject?>?>(null)
|
|
122
|
+
|
|
123
|
+
val resolve = ResolveCallback(future)
|
|
124
|
+
val reject = RejectCallback(future)
|
|
125
|
+
context.registerFunction("_resolveCallback", resolve)
|
|
126
|
+
context.registerFunction("_rejectCallback", reject)
|
|
119
127
|
|
|
120
128
|
val args = JSONObject()
|
|
121
129
|
|
|
@@ -126,15 +134,15 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
126
134
|
args.put("callbacks", callbacks)
|
|
127
135
|
args.put("dataArgs", dataArgs)
|
|
128
136
|
|
|
129
|
-
context
|
|
137
|
+
context.dispatchEvent(config.event, args)
|
|
138
|
+
|
|
139
|
+
runner.waitForJobs()
|
|
130
140
|
|
|
131
141
|
val finished = future.conditionalAwait {
|
|
132
142
|
it != null
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
runner = null
|
|
145
|
+
destroyContext(context)
|
|
138
146
|
|
|
139
147
|
if (finished!!.isSuccess) {
|
|
140
148
|
return finished.getOrNull()
|
|
@@ -158,29 +166,37 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
158
166
|
val plugins = configObject.getJSONObject("plugins") ?: throw Exception("could not read config file")
|
|
159
167
|
val runnerConfigObj = plugins.getJSONObject("BackgroundRunner") ?: throw Exception("could not read config file")
|
|
160
168
|
|
|
161
|
-
return RunnerConfig(runnerConfigObj)
|
|
169
|
+
return RunnerConfig.fromJSON(runnerConfigObj)
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
|
|
165
|
-
private fun initContext(config: RunnerConfig, context: android.content.Context): Context {
|
|
166
|
-
runner ?: throw Exception("runner is not
|
|
173
|
+
private fun initContext(config: RunnerConfig, context: android.content.Context, callbackId: String?): Context {
|
|
174
|
+
val runner = runner ?: throw Exception("runner is not started")
|
|
167
175
|
|
|
168
176
|
val srcFile = context.assets.open("public/${config.src}").bufferedReader().use {
|
|
169
177
|
it.readText()
|
|
170
178
|
}
|
|
171
179
|
|
|
172
|
-
|
|
180
|
+
var contextName = config.label
|
|
181
|
+
if (callbackId != null) {
|
|
182
|
+
contextName += "-$callbackId"
|
|
183
|
+
}
|
|
173
184
|
|
|
174
|
-
val api =
|
|
175
|
-
api.initNotificationsAPI(Notifications(context))
|
|
185
|
+
val api = NativeCapacitorAPI()
|
|
176
186
|
api.initDeviceAPI(Device(context))
|
|
177
|
-
api.initGeolocationAPI(Geolocation(context))
|
|
178
187
|
api.initKVAPI(KV(context, config.label))
|
|
188
|
+
api.initGeolocationAPI(Geolocation(context))
|
|
189
|
+
api.initNotificationsAPI(Notifications(context))
|
|
179
190
|
|
|
180
|
-
newContext.
|
|
181
|
-
|
|
191
|
+
val newContext = runner.createContext(contextName, api)
|
|
182
192
|
newContext.execute(srcFile, false)
|
|
183
193
|
|
|
184
194
|
return newContext
|
|
185
195
|
}
|
|
196
|
+
|
|
197
|
+
private fun destroyContext(context: Context) {
|
|
198
|
+
val runner = this.runner ?: throw Exception("runner is not started")
|
|
199
|
+
|
|
200
|
+
runner.destroyContext(context.name)
|
|
201
|
+
}
|
|
186
202
|
}
|
|
@@ -12,13 +12,14 @@ import com.getcapacitor.annotation.PermissionCallback
|
|
|
12
12
|
import kotlinx.coroutines.Dispatchers
|
|
13
13
|
import kotlinx.coroutines.GlobalScope
|
|
14
14
|
import kotlinx.coroutines.launch
|
|
15
|
+
import kotlinx.coroutines.runBlocking
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@CapacitorPlugin(
|
|
18
19
|
name = "BackgroundRunner",
|
|
19
20
|
permissions = [
|
|
20
21
|
Permission(
|
|
21
|
-
strings = [Manifest.permission.ACCESS_COARSE_LOCATION],
|
|
22
|
+
strings = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION],
|
|
22
23
|
alias = BackgroundRunnerPlugin.GEOLOCATION
|
|
23
24
|
),
|
|
24
25
|
Permission(
|
|
@@ -35,16 +36,28 @@ class BackgroundRunnerPlugin: Plugin() {
|
|
|
35
36
|
const val NOTIFICATIONS = "notifications"
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
override fun handleOnPause() {
|
|
40
|
+
super.handleOnPause()
|
|
41
|
+
Log.d("Background Runner", "registering runner workers")
|
|
42
|
+
impl?.scheduleBackgroundTask(this.context)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun handleOnStop() {
|
|
46
|
+
super.handleOnStop()
|
|
47
|
+
Log.d("Background Runner", "shutting down foreground runner")
|
|
48
|
+
impl?.shutdown()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun handleOnResume() {
|
|
52
|
+
super.handleOnResume()
|
|
53
|
+
Log.d("Background Runner", "starting foreground runner")
|
|
54
|
+
impl?.start()
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
override fun load() {
|
|
39
58
|
super.load()
|
|
40
|
-
impl = BackgroundRunner
|
|
41
|
-
|
|
42
|
-
bridge.app.setStatusChangeListener {
|
|
43
|
-
if (!it) {
|
|
44
|
-
Log.d("Background Runner", "registering runner workers")
|
|
45
|
-
impl?.scheduleBackgroundTask(this.context)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
59
|
+
impl = BackgroundRunner(this.context)
|
|
60
|
+
impl?.start()
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
@PluginMethod
|
|
@@ -66,19 +79,22 @@ class BackgroundRunnerPlugin: Plugin() {
|
|
|
66
79
|
@PluginMethod
|
|
67
80
|
fun dispatchEvent(call: PluginCall) {
|
|
68
81
|
try {
|
|
69
|
-
val impl =
|
|
82
|
+
val impl = this.impl ?: throw Exception("background runner not initialized")
|
|
70
83
|
|
|
71
84
|
val runnerEvent = call.getString("event") ?: throw Exception("event is missing or invalid")
|
|
72
85
|
val details = call.getObject("details")
|
|
73
86
|
val config = impl.config ?: throw Exception("no runner config loaded")
|
|
74
|
-
config.event = runnerEvent
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
val runningConfig = config.copy()
|
|
89
|
+
runningConfig.event = runnerEvent
|
|
90
|
+
|
|
91
|
+
runBlocking(Dispatchers.IO) {
|
|
77
92
|
try {
|
|
78
93
|
val returnData = impl.execute(
|
|
79
94
|
this@BackgroundRunnerPlugin.context,
|
|
80
|
-
|
|
81
|
-
details
|
|
95
|
+
runningConfig,
|
|
96
|
+
details,
|
|
97
|
+
call.callbackId
|
|
82
98
|
)
|
|
83
99
|
|
|
84
100
|
if (returnData != null) {
|
|
@@ -3,31 +3,36 @@ package io.ionic.backgroundrunner.plugin;
|
|
|
3
3
|
import org.json.JSONObject
|
|
4
4
|
import java.lang.Exception
|
|
5
5
|
|
|
6
|
-
class RunnerConfig(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
val interval: Int?
|
|
13
|
-
|
|
14
|
-
init {
|
|
15
|
-
val labelStr = configObject.getString("label")
|
|
16
|
-
if (labelStr.isNullOrEmpty()) {
|
|
17
|
-
throw Exception("runner label is missing or invalid")
|
|
18
|
-
}
|
|
6
|
+
data class RunnerConfig( val label: String,
|
|
7
|
+
val src: String,
|
|
8
|
+
val autoSchedule: Boolean,
|
|
9
|
+
val repeats: Boolean,
|
|
10
|
+
var event: String,
|
|
11
|
+
val interval: Int?) {
|
|
19
12
|
|
|
20
|
-
val srcStr = configObject.getString("src")
|
|
21
|
-
if (srcStr.isNullOrEmpty()) {
|
|
22
|
-
throw Exception("runner source file path is missing or invalid")
|
|
23
|
-
}
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
companion object {
|
|
15
|
+
fun fromJSON(configObject: JSONObject): RunnerConfig {
|
|
16
|
+
val labelStr = configObject.getString("label")
|
|
17
|
+
if (labelStr.isNullOrEmpty()) {
|
|
18
|
+
throw Exception("runner label is missing or invalid")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
val srcStr = configObject.getString("src")
|
|
22
|
+
if (srcStr.isNullOrEmpty()) {
|
|
23
|
+
throw Exception("runner source file path is missing or invalid")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return RunnerConfig(
|
|
27
|
+
labelStr,
|
|
28
|
+
srcStr,
|
|
29
|
+
configObject.optBoolean("autoStart", false),
|
|
30
|
+
configObject.optBoolean("repeat"),
|
|
31
|
+
configObject.optString("event"),
|
|
32
|
+
configObject.optInt("interval", 0)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
31
35
|
}
|
|
36
|
+
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package io.ionic.backgroundrunner.plugin;
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import androidx.work.Data
|
|
5
6
|
import androidx.work.Worker
|
|
6
7
|
import androidx.work.WorkerParameters
|
|
@@ -27,15 +28,20 @@ class RunnerWorker(context: Context, workerParams: WorkerParameters) : Worker(co
|
|
|
27
28
|
runnerConfigObj.put("repeats", false)
|
|
28
29
|
runnerConfigObj.put("interval", 0)
|
|
29
30
|
|
|
30
|
-
val config = RunnerConfig(runnerConfigObj)
|
|
31
|
+
val config = RunnerConfig.fromJSON(runnerConfigObj)
|
|
31
32
|
|
|
32
33
|
runBlocking {
|
|
33
|
-
val impl = BackgroundRunner
|
|
34
|
+
val impl = BackgroundRunner(this@RunnerWorker.applicationContext)
|
|
35
|
+
impl.start()
|
|
34
36
|
impl.execute(this@RunnerWorker.applicationContext, config, JSONObject())
|
|
37
|
+
impl.shutdown()
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
return Result.success()
|
|
38
41
|
} catch (ex: Exception) {
|
|
42
|
+
val label = this.inputData.getString("label") ?: ""
|
|
43
|
+
Log.e("[RUNNER WORKER for $label]", ex.toString())
|
|
44
|
+
ex.printStackTrace()
|
|
39
45
|
val data = Data.Builder()
|
|
40
46
|
.putString("error", ex.toString())
|
|
41
47
|
.build()
|
|
@@ -8,10 +8,9 @@ import android.net.Network
|
|
|
8
8
|
import android.net.NetworkCapabilities
|
|
9
9
|
import android.os.BatteryManager
|
|
10
10
|
import android.os.Build
|
|
11
|
-
import io.ionic.android_js_engine.
|
|
11
|
+
import io.ionic.android_js_engine.capacitor_api.DeviceAPI
|
|
12
12
|
import org.json.JSONObject
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
class Device (context: android.content.Context) : DeviceAPI {
|
|
16
15
|
private val context: android.content.Context
|
|
17
16
|
private val connectivityManager: ConnectivityManager
|
|
@@ -4,7 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import android.location.Location
|
|
5
5
|
import android.location.LocationManager
|
|
6
6
|
import androidx.core.location.LocationManagerCompat
|
|
7
|
-
import io.ionic.android_js_engine.
|
|
7
|
+
import io.ionic.android_js_engine.capacitor_api.GeolocationAPI
|
|
8
8
|
import org.json.JSONObject
|
|
9
9
|
import java.lang.Exception
|
|
10
10
|
|
|
@@ -2,7 +2,7 @@ package io.ionic.backgroundrunner.plugin.api
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.SharedPreferences
|
|
5
|
-
import io.ionic.android_js_engine.
|
|
5
|
+
import io.ionic.android_js_engine.capacitor_api.KVAPI
|
|
6
6
|
|
|
7
7
|
class KV (context: android.content.Context, label: String) : KVAPI {
|
|
8
8
|
private val prefs: SharedPreferences
|
|
@@ -13,14 +13,15 @@ import android.net.Uri
|
|
|
13
13
|
import android.os.Build
|
|
14
14
|
import androidx.core.app.NotificationCompat
|
|
15
15
|
import androidx.core.app.NotificationManagerCompat
|
|
16
|
-
import com.getcapacitor.
|
|
16
|
+
import com.getcapacitor.CapConfig
|
|
17
17
|
import com.getcapacitor.plugin.util.AssetUtil
|
|
18
|
-
import io.ionic.android_js_engine.
|
|
18
|
+
import io.ionic.android_js_engine.capacitor_api.NotificationsAPI
|
|
19
19
|
import org.json.JSONArray
|
|
20
20
|
|
|
21
21
|
class Notifications(context: Context) : NotificationsAPI {
|
|
22
22
|
private val manager: NotificationManagerCompat
|
|
23
23
|
private val context: Context
|
|
24
|
+
private val config: CapConfig
|
|
24
25
|
|
|
25
26
|
var defaultSmallIconID = AssetUtil.RESOURCE_ID_ZERO_VALUE
|
|
26
27
|
var defaultSoundID = AssetUtil.RESOURCE_ID_ZERO_VALUE
|
|
@@ -28,6 +29,7 @@ class Notifications(context: Context) : NotificationsAPI {
|
|
|
28
29
|
init {
|
|
29
30
|
this.context = context
|
|
30
31
|
this.manager = NotificationManagerCompat.from(context)
|
|
32
|
+
this.config = CapConfig.loadDefault(context)
|
|
31
33
|
|
|
32
34
|
this.createNotificationChannel()
|
|
33
35
|
}
|
|
Binary file
|
package/dist/docs-api.json
CHANGED
package/dist/docs.json
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"name": "dispatchEvent",
|
|
51
|
-
"signature": "(options: DispatchEventOptions) => any",
|
|
51
|
+
"signature": "<T = void>(options: DispatchEventOptions) => any",
|
|
52
52
|
"parameters": [
|
|
53
53
|
{
|
|
54
54
|
"name": "options",
|
|
@@ -69,7 +69,8 @@
|
|
|
69
69
|
],
|
|
70
70
|
"docs": "Dispatches an event to the configured runner.",
|
|
71
71
|
"complexTypes": [
|
|
72
|
-
"DispatchEventOptions"
|
|
72
|
+
"DispatchEventOptions",
|
|
73
|
+
"T"
|
|
73
74
|
],
|
|
74
75
|
"slug": "dispatchevent"
|
|
75
76
|
}
|
package/dist/esm/apis.d.ts
CHANGED
|
@@ -249,7 +249,7 @@ export interface CapacitorGeolocation {
|
|
|
249
249
|
* Get the device's last known location
|
|
250
250
|
* @since 1.0.0
|
|
251
251
|
*/
|
|
252
|
-
|
|
252
|
+
getCurrentPosition: () => GetCurrentPositionResult;
|
|
253
253
|
}
|
|
254
254
|
/**
|
|
255
255
|
* A simple string key / value store backed by UserDefaults on iOS and Shared Preferences on Android.
|
package/dist/esm/apis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apis.js","sourceRoot":"","sources":["../../src/apis.ts"],"names":[],"mappings":"","sourcesContent":["export interface GetCurrentPositionResult {\n /**\n * Latitude in decimal degrees\n *\n * @since 1.0.0\n */\n latitude: number;\n /**\n * longitude in decimal degrees\n *\n * @since 1.0.0\n */\n longitude: number;\n /**\n * Accuracy level of the latitude and longitude coordinates in meters\n *\n * @since 1.0.0\n */\n accuracy: number;\n /**\n * The altitude the user is at (if available)\n *\n * @since 1.0.0\n */\n altitude: number | null;\n /**\n * Accuracy level of the altitude coordinate in meters, if available.\n *\n * Available on all iOS versions and on Android 8.0+.\n *\n * @since 1.0.0\n */\n altitudeAccuracy?: number | null | undefined;\n /**\n * The speed the user is traveling (if available)\n *\n * @since 1.0.0\n */\n speed: number | null;\n /**\n * The heading the user is facing (if available)\n *\n * @since 1.0.0\n */\n heading: number | null;\n}\n\nexport interface BatteryStatus {\n batteryLevel: number;\n isCharging: boolean;\n}\n\nexport interface NetworkStatus {\n connected: boolean;\n connectionType: string;\n}\n\nexport interface NotificationScheduleOptions {\n /**\n * The notification identifier.\n * On Android it's a 32-bit int.\n * So the value should be between -2147483648 and 2147483647 inclusive.\n *\n * @since 1.0.0\n */\n id: number;\n /**\n * The title of the notification.\n *\n * @since 1.0.0\n */\n title: string;\n /**\n * The body of the notification, shown below the title.\n *\n * @since 1.0.0\n */\n body: string;\n /**\n * Date to send this notification.\n *\n * @since 1.0.0\n */\n scheduleAt: Date;\n /**\n * Name of the audio file to play when this notification is displayed.\n *\n * Include the file extension with the filename.\n *\n * On iOS, the file should be in the app bundle.\n * On Android, the file should be in res/raw folder.\n *\n * Recommended format is `.wav` because is supported by both iOS and Android.\n *\n * Only available for iOS and Android < 26.\n * For Android 26+ use channelId of a channel configured with the desired sound.\n *\n * If the sound file is not found, (i.e. empty string or wrong name)\n * the default system notification sound will be used.\n * If not provided, it will produce the default sound on Android and no sound on iOS.\n *\n * @since 1.0.0\n */\n sound?: string;\n /**\n * Associate an action type with this notification.\n *\n * @since 1.0.0\n */\n actionTypeId?: string;\n /**\n * Used to group multiple notifications.\n *\n * Sets `threadIdentifier` on the\n * [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent).\n *\n * Only available for iOS.\n *\n * @since 1.0.0\n */\n threadIdentifier?: string;\n /**\n * The string this notification adds to the category's summary format string.\n *\n * Sets `summaryArgument` on the\n * [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent).\n *\n * Only available for iOS.\n *\n * @since 1.0.0\n */\n summaryArgument?: string;\n /**\n * Used to group multiple notifications.\n *\n * Calls `setGroup()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n group?: string;\n /**\n * If true, this notification becomes the summary for a group of\n * notifications.\n *\n * Calls `setGroupSummary()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android when using `group`.\n *\n * @since 1.0.0\n */\n groupSummary?: string;\n /**\n * Set extra data to store within this notification.\n *\n * @since 1.0.0\n */\n extra?: any;\n /**\n * If true, the notification can't be swiped away.\n *\n * Calls `setOngoing()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n ongoing?: boolean;\n /**\n * If true, the notification is canceled when the user clicks on it.\n *\n * Calls `setAutoCancel()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n autoCancel?: boolean;\n /**\n * Sets a multiline text block for display in a big text notification style.\n *\n * @since 1.0.0\n */\n largeBody?: string;\n /**\n * Used to set the summary text detail in inbox and big text notification styles.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n summaryText?: string;\n /**\n * Set a custom status bar icon.\n *\n * If set, this overrides the `smallIcon` option from Capacitor\n * configuration.\n *\n * Icons should be placed in your app's `res/drawable` folder. The value for\n * this option should be the drawable resource ID, which is the filename\n * without an extension.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n smallIcon?: string;\n /**\n * Set a large icon for notifications.\n *\n * Icons should be placed in your app's `res/drawable` folder. The value for\n * this option should be the drawable resource ID, which is the filename\n * without an extension.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n largeIcon?: string;\n /**\n * Specifies the channel the notification should be delivered on.\n *\n * If channel with the given name does not exist then the notification will\n * not fire. If not provided, it will use the default channel.\n *\n * Calls `setChannelId()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android 26+.\n *\n * @since 1.0.0\n */\n channelId?: string;\n}\n\n/**\n * Get access to device location information.\n *\n * @since 1.0.0\n */\nexport interface CapacitorGeolocation {\n /**\n * Get the device's last known location\n * @since 1.0.0\n */\n
|
|
1
|
+
{"version":3,"file":"apis.js","sourceRoot":"","sources":["../../src/apis.ts"],"names":[],"mappings":"","sourcesContent":["export interface GetCurrentPositionResult {\n /**\n * Latitude in decimal degrees\n *\n * @since 1.0.0\n */\n latitude: number;\n /**\n * longitude in decimal degrees\n *\n * @since 1.0.0\n */\n longitude: number;\n /**\n * Accuracy level of the latitude and longitude coordinates in meters\n *\n * @since 1.0.0\n */\n accuracy: number;\n /**\n * The altitude the user is at (if available)\n *\n * @since 1.0.0\n */\n altitude: number | null;\n /**\n * Accuracy level of the altitude coordinate in meters, if available.\n *\n * Available on all iOS versions and on Android 8.0+.\n *\n * @since 1.0.0\n */\n altitudeAccuracy?: number | null | undefined;\n /**\n * The speed the user is traveling (if available)\n *\n * @since 1.0.0\n */\n speed: number | null;\n /**\n * The heading the user is facing (if available)\n *\n * @since 1.0.0\n */\n heading: number | null;\n}\n\nexport interface BatteryStatus {\n batteryLevel: number;\n isCharging: boolean;\n}\n\nexport interface NetworkStatus {\n connected: boolean;\n connectionType: string;\n}\n\nexport interface NotificationScheduleOptions {\n /**\n * The notification identifier.\n * On Android it's a 32-bit int.\n * So the value should be between -2147483648 and 2147483647 inclusive.\n *\n * @since 1.0.0\n */\n id: number;\n /**\n * The title of the notification.\n *\n * @since 1.0.0\n */\n title: string;\n /**\n * The body of the notification, shown below the title.\n *\n * @since 1.0.0\n */\n body: string;\n /**\n * Date to send this notification.\n *\n * @since 1.0.0\n */\n scheduleAt: Date;\n /**\n * Name of the audio file to play when this notification is displayed.\n *\n * Include the file extension with the filename.\n *\n * On iOS, the file should be in the app bundle.\n * On Android, the file should be in res/raw folder.\n *\n * Recommended format is `.wav` because is supported by both iOS and Android.\n *\n * Only available for iOS and Android < 26.\n * For Android 26+ use channelId of a channel configured with the desired sound.\n *\n * If the sound file is not found, (i.e. empty string or wrong name)\n * the default system notification sound will be used.\n * If not provided, it will produce the default sound on Android and no sound on iOS.\n *\n * @since 1.0.0\n */\n sound?: string;\n /**\n * Associate an action type with this notification.\n *\n * @since 1.0.0\n */\n actionTypeId?: string;\n /**\n * Used to group multiple notifications.\n *\n * Sets `threadIdentifier` on the\n * [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent).\n *\n * Only available for iOS.\n *\n * @since 1.0.0\n */\n threadIdentifier?: string;\n /**\n * The string this notification adds to the category's summary format string.\n *\n * Sets `summaryArgument` on the\n * [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent).\n *\n * Only available for iOS.\n *\n * @since 1.0.0\n */\n summaryArgument?: string;\n /**\n * Used to group multiple notifications.\n *\n * Calls `setGroup()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n group?: string;\n /**\n * If true, this notification becomes the summary for a group of\n * notifications.\n *\n * Calls `setGroupSummary()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android when using `group`.\n *\n * @since 1.0.0\n */\n groupSummary?: string;\n /**\n * Set extra data to store within this notification.\n *\n * @since 1.0.0\n */\n extra?: any;\n /**\n * If true, the notification can't be swiped away.\n *\n * Calls `setOngoing()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n ongoing?: boolean;\n /**\n * If true, the notification is canceled when the user clicks on it.\n *\n * Calls `setAutoCancel()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n autoCancel?: boolean;\n /**\n * Sets a multiline text block for display in a big text notification style.\n *\n * @since 1.0.0\n */\n largeBody?: string;\n /**\n * Used to set the summary text detail in inbox and big text notification styles.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n summaryText?: string;\n /**\n * Set a custom status bar icon.\n *\n * If set, this overrides the `smallIcon` option from Capacitor\n * configuration.\n *\n * Icons should be placed in your app's `res/drawable` folder. The value for\n * this option should be the drawable resource ID, which is the filename\n * without an extension.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n smallIcon?: string;\n /**\n * Set a large icon for notifications.\n *\n * Icons should be placed in your app's `res/drawable` folder. The value for\n * this option should be the drawable resource ID, which is the filename\n * without an extension.\n *\n * Only available for Android.\n *\n * @since 1.0.0\n */\n largeIcon?: string;\n /**\n * Specifies the channel the notification should be delivered on.\n *\n * If channel with the given name does not exist then the notification will\n * not fire. If not provided, it will use the default channel.\n *\n * Calls `setChannelId()` on\n * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder)\n * with the provided value.\n *\n * Only available for Android 26+.\n *\n * @since 1.0.0\n */\n channelId?: string;\n}\n\n/**\n * Get access to device location information.\n *\n * @since 1.0.0\n */\nexport interface CapacitorGeolocation {\n /**\n * Get the device's last known location\n * @since 1.0.0\n */\n getCurrentPosition: () => GetCurrentPositionResult;\n}\n\n/**\n * A simple string key / value store backed by UserDefaults on iOS and Shared Preferences on Android.\n */\nexport interface CapacitorKV {\n /**\n * Set a string value with the given key.\n *\n * @param key\n * @param value\n * @returns\n * @since 1.0.0\n */\n set: (key: string, value: string) => void;\n /**\n * Get a string value for the given key.\n * @param key\n * @returns\n * @since 1.0.0\n */\n get: (key: string) => { value: string };\n /**\n * Remove a value with the given key.\n * @param key\n * @returns\n * @since 1.0.0\n */\n remove: (key: string) => void;\n}\n\n/**\n * Get information on the device, such as network connectivity and battery status.\n */\nexport interface CapacitorDevice {\n /**\n * Get the current battery status for the device.\n *\n * @since 1.0.0\n */\n getBatteryStatus: () => BatteryStatus;\n /**\n * Get the current network status for the device.\n *\n * @since 1.0.0\n */\n getNetworkStatus: () => NetworkStatus;\n}\n\n/**\n * Send basic local notifications.\n */\nexport interface CapacitorNotifications {\n /**\n * Schedule a local notification\n * @example `CapacitorNotifications.schedule()`\n * @param options\n * @returns void\n * @since 1.0.0\n */\n schedule: (options: NotificationScheduleOptions[]) => void;\n}\n\n/**\n * Interact with a watch paired with this app\n *\n * sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application.\n * They could be used if a native watch app is developed as a companion app to a Capacitor app\n */\nexport interface CapcacitorWatch {\n /**\n * Sends a message to the watch with the sendMessage() WCSession delegate method\n *\n * This has no effect on a CapacitorWatch watch app\n */\n sendMessage: (options: []) => void;\n /**\n * Sends information to the watch with the transferUserInfo() WCSession delegate method\n *\n * This has no effect on a CapacitorWatch watch app\n */\n transferUserInfo: (options: []) => void;\n /**\n * Updates the application context on the watch with the updateApplicationContext() WCSession delegate method\n *\n * This has no effect on a CapacitorWatch watch app\n */\n updateApplicationContext: (options: []) => void;\n /**\n * Checks to see if the compaion watch is reachable\n */\n isReachable: boolean;\n /**\n * Replaces the current UI on the watch with what is specified here.\n * @param options the new WatchUI you want to show on the watch\n * @returns void\n */\n updateWatchUI: (options: { watchUI: string }) => void;\n /**\n * Updates the data the watch is using to display variables in text and button fields\n * @param options the new data to send to the watch\n * @returns void\n */\n updateWatchData: (options: { data: { [key: string]: string } }) => void;\n}\n\nexport interface CapacitorAPI {\n CapacitorDevice: CapacitorDevice;\n CapacitorKV: CapacitorKV;\n CapacitorNotifications: CapacitorNotifications;\n CapacitorGeolocation: CapacitorGeolocation;\n CapacitorWatch: CapcacitorWatch;\n}\n"]}
|
|
@@ -140,7 +140,7 @@ export interface BackgroundRunnerPlugin {
|
|
|
140
140
|
* @param options
|
|
141
141
|
* @since 1.0.0
|
|
142
142
|
*/
|
|
143
|
-
dispatchEvent(options: DispatchEventOptions): Promise<
|
|
143
|
+
dispatchEvent<T = void>(options: DispatchEventOptions): Promise<T>;
|
|
144
144
|
}
|
|
145
145
|
declare module '@capacitor/cli' {
|
|
146
146
|
interface PluginsConfig {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PermissionState } from '@capacitor/core';\n\nexport interface BackgroundRunnerConfig {\n /**\n * The name of the runner, used in logs.\n *\n * @since 1.0.0\n */\n label: string;\n /**\n * The path to the runner JavaScript file, relative to the app bundle.\n *\n * @since 1.0.0\n */\n src: string;\n /**\n * The name of the event that will be called when the OS executes the background task.\n *\n * @since 1.0.0\n */\n event: string;\n /**\n * If background task should repeat based on the interval set in `interval`.\n *\n * @since 1.0.0\n */\n repeat: boolean;\n /**\n * The number of minutes after the the app is put into the background in which the background task should begin.\n *\n * If `repeat` is true, this also specifies the number of minutes between each execution.\n *\n * @since 1.0.0\n */\n interval: number;\n /**\n * Automatically register and schedule background task on app load.\n *\n * @since 1.0.0\n */\n autoStart?: boolean;\n}\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n /**\n * On load, Background Runner will automatically register a\n * background task that will be scheduled and ran once your app is\n * backgrounded.\n */\n BackgroundRunner?: {\n /**\n * The name of the runner, used in logs.\n *\n * @since 1.0.0\n * @example \"com.example.background.task\"\n */\n label: string;\n /**\n * The path to the runner JavaScript file, relative to the app bundle.\n *\n * @since 1.0.0\n * @example \"runners/background.js\"\n */\n src: string;\n /**\n * The name of the event that will be called when the OS executes the background task.\n *\n * @since 1.0.0\n * @example \"myCustomEvent\"\n */\n event: string;\n /**\n * If background task should repeat based on the interval set in `interval`.\n *\n * @since 1.0.0\n * @example true\n */\n repeat: boolean;\n /**\n * The number of minutes after the the app is put into the background in which the background task should begin.\n *\n * If `repeat` is true, this also specifies the number of minutes between each execution.\n *\n * @since 1.0.0\n * @example 15\n */\n interval: number;\n /**\n * Automatically register and schedule background task on app load.\n *\n * @since 1.0.0\n * @example true\n */\n autoStart?: boolean;\n };\n }\n}\n\nexport type API = 'geolocation' | 'notifications';\n\nexport interface PermissionStatus {\n geolocation: PermissionState;\n notifications: PermissionState;\n}\n\nexport interface DispatchEventOptions {\n /**\n * The runner label to dispatch the event to\n *\n * @since 1.0.0\n */\n label: string;\n /**\n * The name of the registered event listener.\n *\n * @since 1.0.0\n */\n event: string;\n details: { [key: string]: any };\n}\n\nexport interface RequestPermissionOptions {\n apis: API[];\n}\n\nexport interface RegisterBackgroundTaskOptions {\n runner: BackgroundRunnerConfig;\n}\n\nexport interface BackgroundRunnerPlugin {\n /**\n * Check permissions for the various Capacitor device APIs.\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise<PermissionStatus>;\n /**\n * Request permission to display local notifications.\n *\n * @since 1.0.0\n */\n requestPermissions(\n options: RequestPermissionOptions,\n ): Promise<PermissionStatus>;\n /**\n * Dispatches an event to the configured runner.\n * @param options\n * @since 1.0.0\n */\n dispatchEvent(options: DispatchEventOptions): Promise<
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PermissionState } from '@capacitor/core';\n\nexport interface BackgroundRunnerConfig {\n /**\n * The name of the runner, used in logs.\n *\n * @since 1.0.0\n */\n label: string;\n /**\n * The path to the runner JavaScript file, relative to the app bundle.\n *\n * @since 1.0.0\n */\n src: string;\n /**\n * The name of the event that will be called when the OS executes the background task.\n *\n * @since 1.0.0\n */\n event: string;\n /**\n * If background task should repeat based on the interval set in `interval`.\n *\n * @since 1.0.0\n */\n repeat: boolean;\n /**\n * The number of minutes after the the app is put into the background in which the background task should begin.\n *\n * If `repeat` is true, this also specifies the number of minutes between each execution.\n *\n * @since 1.0.0\n */\n interval: number;\n /**\n * Automatically register and schedule background task on app load.\n *\n * @since 1.0.0\n */\n autoStart?: boolean;\n}\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n /**\n * On load, Background Runner will automatically register a\n * background task that will be scheduled and ran once your app is\n * backgrounded.\n */\n BackgroundRunner?: {\n /**\n * The name of the runner, used in logs.\n *\n * @since 1.0.0\n * @example \"com.example.background.task\"\n */\n label: string;\n /**\n * The path to the runner JavaScript file, relative to the app bundle.\n *\n * @since 1.0.0\n * @example \"runners/background.js\"\n */\n src: string;\n /**\n * The name of the event that will be called when the OS executes the background task.\n *\n * @since 1.0.0\n * @example \"myCustomEvent\"\n */\n event: string;\n /**\n * If background task should repeat based on the interval set in `interval`.\n *\n * @since 1.0.0\n * @example true\n */\n repeat: boolean;\n /**\n * The number of minutes after the the app is put into the background in which the background task should begin.\n *\n * If `repeat` is true, this also specifies the number of minutes between each execution.\n *\n * @since 1.0.0\n * @example 15\n */\n interval: number;\n /**\n * Automatically register and schedule background task on app load.\n *\n * @since 1.0.0\n * @example true\n */\n autoStart?: boolean;\n };\n }\n}\n\nexport type API = 'geolocation' | 'notifications';\n\nexport interface PermissionStatus {\n geolocation: PermissionState;\n notifications: PermissionState;\n}\n\nexport interface DispatchEventOptions {\n /**\n * The runner label to dispatch the event to\n *\n * @since 1.0.0\n */\n label: string;\n /**\n * The name of the registered event listener.\n *\n * @since 1.0.0\n */\n event: string;\n details: { [key: string]: any };\n}\n\nexport interface RequestPermissionOptions {\n apis: API[];\n}\n\nexport interface RegisterBackgroundTaskOptions {\n runner: BackgroundRunnerConfig;\n}\n\nexport interface BackgroundRunnerPlugin {\n /**\n * Check permissions for the various Capacitor device APIs.\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise<PermissionStatus>;\n /**\n * Request permission to display local notifications.\n *\n * @since 1.0.0\n */\n requestPermissions(\n options: RequestPermissionOptions,\n ): Promise<PermissionStatus>;\n /**\n * Dispatches an event to the configured runner.\n * @param options\n * @since 1.0.0\n */\n dispatchEvent<T = void>(options: DispatchEventOptions): Promise<T>;\n}\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n BackgroundRunner?: BackgroundRunnerConfig;\n }\n}\n"]}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
|
-
import type { BackgroundRunnerPlugin, PermissionStatus } from './definitions';
|
|
2
|
+
import type { BackgroundRunnerPlugin, DispatchEventOptions, PermissionStatus } from './definitions';
|
|
3
3
|
export declare class BackgroundRunnerWeb extends WebPlugin implements BackgroundRunnerPlugin {
|
|
4
4
|
checkPermissions(): Promise<PermissionStatus>;
|
|
5
5
|
requestPermissions(): Promise<PermissionStatus>;
|
|
6
6
|
registerBackgroundTask(): Promise<void>;
|
|
7
|
-
dispatchEvent(): Promise<
|
|
7
|
+
dispatchEvent<T = void>(_options: DispatchEventOptions): Promise<T>;
|
|
8
8
|
}
|
package/dist/esm/web.js
CHANGED
package/dist/esm/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,MAAM,OAAO,mBACX,SAAQ,SAAS;IAGjB,gBAAgB;QACd,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IAED,kBAAkB;QAChB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IAED,sBAAsB;QACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IAED,aAAa,CAAW,QAA8B;QACpD,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type {\n BackgroundRunnerPlugin,\n DispatchEventOptions,\n PermissionStatus,\n} from './definitions';\n\nexport class BackgroundRunnerWeb\n extends WebPlugin\n implements BackgroundRunnerPlugin\n{\n checkPermissions(): Promise<PermissionStatus> {\n throw new Error('not available on web');\n }\n\n requestPermissions(): Promise<PermissionStatus> {\n throw new Error('not available on web');\n }\n\n registerBackgroundTask(): Promise<void> {\n throw new Error('not available on web');\n }\n\n dispatchEvent<T = void>(_options: DispatchEventOptions): Promise<T> {\n throw new Error('not available on web');\n }\n}\n"]}
|
package/dist/plugin.cjs.js
CHANGED
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst BackgroundRunner = registerPlugin('BackgroundRunner', {\n web: () => import('./web').then(m => new m.BackgroundRunnerWeb()),\n});\nexport * from './definitions';\nexport * from './apis';\nexport { BackgroundRunner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class BackgroundRunnerWeb extends WebPlugin {\n checkPermissions() {\n throw new Error('not available on web');\n }\n requestPermissions() {\n throw new Error('not available on web');\n }\n registerBackgroundTask() {\n throw new Error('not available on web');\n }\n dispatchEvent() {\n throw new Error('not available on web');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;;;AACK,MAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACrE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,gBAAgB,GAAG;AACvB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,kBAAkB,GAAG;AACzB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,sBAAsB,GAAG;AAC7B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst BackgroundRunner = registerPlugin('BackgroundRunner', {\n web: () => import('./web').then(m => new m.BackgroundRunnerWeb()),\n});\nexport * from './definitions';\nexport * from './apis';\nexport { BackgroundRunner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class BackgroundRunnerWeb extends WebPlugin {\n checkPermissions() {\n throw new Error('not available on web');\n }\n requestPermissions() {\n throw new Error('not available on web');\n }\n registerBackgroundTask() {\n throw new Error('not available on web');\n }\n dispatchEvent(_options) {\n throw new Error('not available on web');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;;;AACK,MAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACrE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,gBAAgB,GAAG;AACvB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,kBAAkB,GAAG;AACzB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,sBAAsB,GAAG;AAC7B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL,IAAI,aAAa,CAAC,QAAQ,EAAE;AAC5B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL;;;;;;;;;"}
|
package/dist/plugin.js
CHANGED
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst BackgroundRunner = registerPlugin('BackgroundRunner', {\n web: () => import('./web').then(m => new m.BackgroundRunnerWeb()),\n});\nexport * from './definitions';\nexport * from './apis';\nexport { BackgroundRunner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class BackgroundRunnerWeb extends WebPlugin {\n checkPermissions() {\n throw new Error('not available on web');\n }\n requestPermissions() {\n throw new Error('not available on web');\n }\n registerBackgroundTask() {\n throw new Error('not available on web');\n }\n dispatchEvent() {\n throw new Error('not available on web');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACrE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,gBAAgB,GAAG;IACvB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,kBAAkB,GAAG;IACzB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,sBAAsB,GAAG;IAC7B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst BackgroundRunner = registerPlugin('BackgroundRunner', {\n web: () => import('./web').then(m => new m.BackgroundRunnerWeb()),\n});\nexport * from './definitions';\nexport * from './apis';\nexport { BackgroundRunner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class BackgroundRunnerWeb extends WebPlugin {\n checkPermissions() {\n throw new Error('not available on web');\n }\n requestPermissions() {\n throw new Error('not available on web');\n }\n registerBackgroundTask() {\n throw new Error('not available on web');\n }\n dispatchEvent(_options) {\n throw new Error('not available on web');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACrE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,gBAAgB,GAAG;IACvB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,kBAAkB,GAAG;IACzB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,sBAAsB,GAAG;IAC7B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL,IAAI,aAAa,CAAC,QAAQ,EAAE;IAC5B,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL;;;;;;;;;;;;;;;;;"}
|
|
@@ -6,18 +6,13 @@ import JavaScriptCore
|
|
|
6
6
|
public class BackgroundRunner {
|
|
7
7
|
public static let shared = BackgroundRunner()
|
|
8
8
|
|
|
9
|
-
private var runner: Runner?
|
|
10
9
|
private var config: RunnerConfig?
|
|
11
|
-
|
|
12
|
-
private var context: Context?
|
|
13
|
-
|
|
14
|
-
private var started: Bool {
|
|
15
|
-
return runner != nil && context != nil
|
|
16
|
-
}
|
|
10
|
+
private var runner = Runner()
|
|
17
11
|
|
|
18
12
|
public init() {
|
|
19
13
|
do {
|
|
20
14
|
config = try self.loadRunnerConfig()
|
|
15
|
+
runner.start()
|
|
21
16
|
} catch {
|
|
22
17
|
print("could not initialize BackgroundRunner: \(error)")
|
|
23
18
|
}
|
|
@@ -76,40 +71,11 @@ public class BackgroundRunner {
|
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
73
|
|
|
79
|
-
public func start() throws {
|
|
80
|
-
print("starting runner and loading contexts...")
|
|
81
|
-
|
|
82
|
-
guard let config = config else {
|
|
83
|
-
print("...no runner config to start")
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
self.runner = Runner()
|
|
88
|
-
context = try initContext(config: config)
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
public func stop() {
|
|
93
|
-
print("...stopping runner and removing all contexts")
|
|
94
|
-
guard let runner = runner, let config = config else {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
runner.destroyContext(name: config.label)
|
|
99
|
-
|
|
100
|
-
context = nil
|
|
101
|
-
self.runner = nil
|
|
102
|
-
}
|
|
103
|
-
|
|
104
74
|
public func getConfig() -> RunnerConfig? {
|
|
105
75
|
return config
|
|
106
76
|
}
|
|
107
77
|
|
|
108
|
-
public func dispatchEvent(event: String, inputArgs: [String: Any]?) throws {
|
|
109
|
-
if !started {
|
|
110
|
-
try start()
|
|
111
|
-
}
|
|
112
|
-
|
|
78
|
+
public func dispatchEvent(event: String, inputArgs: [String: Any]?, callbackId: String? = nil) throws {
|
|
113
79
|
if let config = config {
|
|
114
80
|
let waitGroup = DispatchGroup()
|
|
115
81
|
waitGroup.enter()
|
|
@@ -119,7 +85,7 @@ public class BackgroundRunner {
|
|
|
119
85
|
// swiftlint:disable:next unowned_variable_capture
|
|
120
86
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
121
87
|
do {
|
|
122
|
-
_ = try self.execute(config: config, event: event, inputArgs: inputArgs)
|
|
88
|
+
_ = try self.execute(config: config, event: event, inputArgs: inputArgs, callbackId: callbackId)
|
|
123
89
|
} catch {
|
|
124
90
|
err = error
|
|
125
91
|
print("[\(config.label)]: \(error)")
|
|
@@ -137,15 +103,9 @@ public class BackgroundRunner {
|
|
|
137
103
|
}
|
|
138
104
|
|
|
139
105
|
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
|
140
|
-
public func execute(config: RunnerConfig, event: String, inputArgs: [String: Any]? = nil) throws -> [String: Any]? {
|
|
106
|
+
public func execute(config: RunnerConfig, event: String, inputArgs: [String: Any]? = nil, callbackId: String? = nil) throws -> [String: Any]? {
|
|
141
107
|
do {
|
|
142
|
-
|
|
143
|
-
try self.start()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
guard let context = context else {
|
|
147
|
-
throw BackgroundRunnerPluginError.runnerError(reason: "no loaded context for config")
|
|
148
|
-
}
|
|
108
|
+
let context = try initContext(config: config, callbackId: callbackId)
|
|
149
109
|
|
|
150
110
|
let waitGroup = DispatchGroup()
|
|
151
111
|
|
|
@@ -241,18 +201,19 @@ public class BackgroundRunner {
|
|
|
241
201
|
return nil
|
|
242
202
|
}
|
|
243
203
|
|
|
244
|
-
private func initContext(config: RunnerConfig) throws -> Context {
|
|
245
|
-
guard let runner = runner else {
|
|
246
|
-
throw BackgroundRunnerPluginError.runnerError(reason: "runner is not initialized")
|
|
247
|
-
}
|
|
248
|
-
|
|
204
|
+
private func initContext(config: RunnerConfig, callbackId: String?) throws -> Context {
|
|
249
205
|
guard let srcFileURL = Bundle.main.url(forResource: config.src, withExtension: nil, subdirectory: "public") else {
|
|
250
206
|
throw BackgroundRunnerPluginError.runnerError(reason: "source file not found")
|
|
251
207
|
}
|
|
252
208
|
|
|
209
|
+
var contextName = config.label
|
|
210
|
+
if let callbackId = callbackId {
|
|
211
|
+
contextName = "\(contextName)-\(callbackId)"
|
|
212
|
+
}
|
|
213
|
+
|
|
253
214
|
let srcFile = try String(contentsOf: srcFileURL)
|
|
254
215
|
|
|
255
|
-
let context = try runner.createContext(name:
|
|
216
|
+
let context = try runner.createContext(name: contextName)
|
|
256
217
|
|
|
257
218
|
context.setupCapacitorAPI()
|
|
258
219
|
|
|
@@ -70,7 +70,8 @@ public class BackgroundRunnerPlugin: CAPPlugin {
|
|
|
70
70
|
let result = try self.impl.execute(
|
|
71
71
|
config: config,
|
|
72
72
|
event: runnerEvent,
|
|
73
|
-
inputArgs: details as [String: Any]
|
|
73
|
+
inputArgs: details as [String: Any],
|
|
74
|
+
callbackId: call.callbackId
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
if let result = result {
|
|
@@ -2,10 +2,10 @@ import Foundation
|
|
|
2
2
|
|
|
3
3
|
extension Context {
|
|
4
4
|
public func setupCapacitorAPI() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
jsContext.setObject(CapacitorKVStore.self, forKeyedSubscript: "CapacitorKV" as NSString)
|
|
6
|
+
jsContext.setObject(CapacitorGeolocation(context: self), forKeyedSubscript: "CapacitorGeolocation" as NSString)
|
|
7
|
+
jsContext.setObject(CapacitorWatch(), forKeyedSubscript: "CapacitorWatch" as NSString)
|
|
8
|
+
jsContext.setObject(CapacitorNotifications.self, forKeyedSubscript: "CapacitorNotifications" as NSString)
|
|
9
|
+
jsContext.setObject(CapacitorDevice(), forKeyedSubscript: "CapacitorDevice" as NSString)
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -3,37 +3,21 @@ import JavaScriptCore
|
|
|
3
3
|
|
|
4
4
|
public class Context {
|
|
5
5
|
let name: String
|
|
6
|
-
let
|
|
6
|
+
let jsContext: JSContext
|
|
7
7
|
|
|
8
|
-
private var timers = [Int: Timer]()
|
|
9
8
|
private var eventListeners = [String: JSValue]()
|
|
10
|
-
private var
|
|
11
|
-
private var runLoop: RunLoop?
|
|
9
|
+
private var timers = [Int: Timer]()
|
|
10
|
+
private weak var runLoop: RunLoop?
|
|
12
11
|
|
|
13
12
|
// swiftlint:disable:next identifier_name
|
|
14
|
-
public init(vm: JSVirtualMachine,
|
|
15
|
-
guard let
|
|
13
|
+
public init(vm: JSVirtualMachine, contextName: String, runLoop: RunLoop) throws {
|
|
14
|
+
guard let jsContext = JSContext(virtualMachine: vm) else {
|
|
16
15
|
throw EngineError.jsCoreError
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ctx = newCtx
|
|
23
|
-
|
|
24
|
-
thread = Thread { [weak self] in
|
|
25
|
-
self?.runLoop = RunLoop.current
|
|
26
|
-
|
|
27
|
-
while self != nil && !self!.thread.isCancelled && self!.runLoop != nil {
|
|
28
|
-
self?.runLoop?.run(mode: .default, before: .distantFuture)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
Thread.exit()
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
thread.name = "[\(name)] context thread"
|
|
35
|
-
thread.qualityOfService = .userInitiated
|
|
36
|
-
thread.start()
|
|
18
|
+
self.name = contextName
|
|
19
|
+
self.jsContext = jsContext
|
|
20
|
+
self.runLoop = runLoop
|
|
37
21
|
|
|
38
22
|
try setupWebAPI()
|
|
39
23
|
}
|
|
@@ -41,11 +25,11 @@ public class Context {
|
|
|
41
25
|
public func execute(code: String) throws -> JSValue? {
|
|
42
26
|
var thrownException: JSValue?
|
|
43
27
|
|
|
44
|
-
|
|
28
|
+
jsContext.exceptionHandler = { _, exception in
|
|
45
29
|
thrownException = exception
|
|
46
30
|
}
|
|
47
31
|
|
|
48
|
-
let value =
|
|
32
|
+
let value = jsContext.evaluateScript(code)
|
|
49
33
|
|
|
50
34
|
if let exception = thrownException {
|
|
51
35
|
throw EngineError.jsException(details: String(describing: exception))
|
|
@@ -67,14 +51,14 @@ public class Context {
|
|
|
67
51
|
callbacks.keys.forEach { key in
|
|
68
52
|
if key.hasPrefix("__cbr::") {
|
|
69
53
|
let funcName = key.replacingOccurrences(of: "__cbr::", with: "")
|
|
70
|
-
callbackFunctions[funcName] = JSValue.init(object: callbacks[key], in: self.
|
|
54
|
+
callbackFunctions[funcName] = JSValue.init(object: callbacks[key], in: self.jsContext)
|
|
71
55
|
}
|
|
72
56
|
}
|
|
73
57
|
}
|
|
74
58
|
|
|
75
59
|
var thrownException: JSValue?
|
|
76
60
|
|
|
77
|
-
self.
|
|
61
|
+
self.jsContext.exceptionHandler = { _, exception in
|
|
78
62
|
thrownException = exception
|
|
79
63
|
}
|
|
80
64
|
|
|
@@ -87,7 +71,7 @@ public class Context {
|
|
|
87
71
|
}
|
|
88
72
|
|
|
89
73
|
if let dataArgs = dataArgs {
|
|
90
|
-
guard let jsDataArgs = JSValue(object: dataArgs, in: self.
|
|
74
|
+
guard let jsDataArgs = JSValue(object: dataArgs, in: self.jsContext) else {
|
|
91
75
|
throw EngineError.jsValueError
|
|
92
76
|
}
|
|
93
77
|
|
|
@@ -99,9 +83,9 @@ public class Context {
|
|
|
99
83
|
if let exception = thrownException {
|
|
100
84
|
throw EngineError.jsException(details: String(describing: exception))
|
|
101
85
|
}
|
|
102
|
-
|
|
103
86
|
}
|
|
104
87
|
}
|
|
88
|
+
|
|
105
89
|
private func setupWebAPI() throws {
|
|
106
90
|
let consoleObj = JSConsole(name: name)
|
|
107
91
|
let addEventListenerFunc: @convention(block)(String, JSValue) -> Void = { type, listener in
|
|
@@ -123,16 +107,16 @@ public class Context {
|
|
|
123
107
|
let newTextEncoderConst: @convention(block) () -> JSTextEncoder = JSTextEncoder.init
|
|
124
108
|
let newTextDecoderConst: @convention(block) (String?, [AnyHashable: Any]?) -> JSTextDecoder = JSTextDecoder.init
|
|
125
109
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
110
|
+
jsContext.setObject(consoleObj, forKeyedSubscript: "console" as NSString)
|
|
111
|
+
jsContext.setObject(fetchFunc, forKeyedSubscript: "fetch" as NSString)
|
|
112
|
+
jsContext.setObject(JSCrypto.self, forKeyedSubscript: "crypto" as NSString)
|
|
113
|
+
jsContext.setObject(addEventListenerFunc, forKeyedSubscript: "addEventListener" as NSString)
|
|
114
|
+
jsContext.setObject(setTimeoutFunc, forKeyedSubscript: "setTimeout" as NSString)
|
|
115
|
+
jsContext.setObject(setIntervalFunc, forKeyedSubscript: "setInterval" as NSString)
|
|
116
|
+
jsContext.setObject(clearTimeoutFunc, forKeyedSubscript: "clearTimeout" as NSString)
|
|
117
|
+
jsContext.setObject(clearTimeoutFunc, forKeyedSubscript: "clearInterval" as NSString)
|
|
118
|
+
jsContext.setObject(newTextEncoderConst, forKeyedSubscript: "TextEncoder" as NSString)
|
|
119
|
+
jsContext.setObject(newTextDecoderConst, forKeyedSubscript: "TextDecoder" as NSString)
|
|
136
120
|
|
|
137
121
|
}
|
|
138
122
|
|
|
@@ -141,7 +125,9 @@ public class Context {
|
|
|
141
125
|
}
|
|
142
126
|
|
|
143
127
|
private func initTimer(callback: JSValue, timeout: Int, isInterval: Bool) -> Int {
|
|
144
|
-
let timer = Timer(timeInterval: TimeInterval(timeout / 1000), repeats: isInterval) { t in
|
|
128
|
+
let timer = Timer(timeInterval: TimeInterval(timeout / 1000), repeats: isInterval) { [weak self] t in
|
|
129
|
+
guard let self = self else { return }
|
|
130
|
+
|
|
145
131
|
callback.call(withArguments: [])
|
|
146
132
|
if !isInterval {
|
|
147
133
|
let timerId = Int(Int32.init(truncatingIfNeeded: t.hashValue))
|
|
@@ -155,9 +141,6 @@ public class Context {
|
|
|
155
141
|
if let runLoop = self.runLoop {
|
|
156
142
|
runLoop.add(timer, forMode: .default)
|
|
157
143
|
runLoop.add(timer, forMode: .common)
|
|
158
|
-
} else {
|
|
159
|
-
RunLoop.current.add(timer, forMode: .default)
|
|
160
|
-
RunLoop.current.add(timer, forMode: .common)
|
|
161
144
|
}
|
|
162
145
|
|
|
163
146
|
return timerId
|
|
@@ -4,20 +4,60 @@ import JavaScriptCore
|
|
|
4
4
|
public class Runner {
|
|
5
5
|
var machine: JSVirtualMachine
|
|
6
6
|
var contexts: [String: Context]
|
|
7
|
+
|
|
8
|
+
private let runLoop: RunLoop
|
|
9
|
+
private var thread: Thread?
|
|
10
|
+
|
|
7
11
|
public init() {
|
|
8
12
|
machine = JSVirtualMachine()
|
|
9
13
|
contexts = [:]
|
|
14
|
+
runLoop = RunLoop.current
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
func createContext(name: String) throws -> Context {
|
|
13
|
-
let
|
|
18
|
+
let context = try Context(vm: machine, contextName: name, runLoop: runLoop)
|
|
19
|
+
|
|
20
|
+
if contexts.keys.contains(name) {
|
|
21
|
+
throw EngineError.runnerError(details: "context with name \(name) already exists")
|
|
22
|
+
}
|
|
14
23
|
|
|
15
|
-
contexts[name] =
|
|
24
|
+
contexts[name] = context
|
|
16
25
|
|
|
17
|
-
return
|
|
26
|
+
return context
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
func destroyContext(name: String) {
|
|
21
30
|
contexts.removeValue(forKey: name)
|
|
22
31
|
}
|
|
32
|
+
|
|
33
|
+
func start() {
|
|
34
|
+
thread = Thread { [weak self] in
|
|
35
|
+
defer {
|
|
36
|
+
Thread.exit()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
guard let self = self else { return }
|
|
40
|
+
guard let thread = self.thread else { return }
|
|
41
|
+
|
|
42
|
+
while !thread.isCancelled {
|
|
43
|
+
self.runLoop.run(mode: .default, before: .distantFuture)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
thread?.qualityOfService = .userInitiated
|
|
48
|
+
thread?.start()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func stop() {
|
|
52
|
+
guard let thread = self.thread else {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
thread.cancel()
|
|
57
|
+
while !thread.isFinished {
|
|
58
|
+
Thread.sleep(forTimeInterval: 0.05)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
self.thread = nil
|
|
62
|
+
}
|
|
23
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor/background-runner",
|
|
3
|
-
"version": "1.0.6-dev-
|
|
3
|
+
"version": "1.0.6-dev-20240409T144959.0",
|
|
4
4
|
"description": "Capacitor Background Runner",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"docgen-api": "docgen --api CapacitorAPI --output-readme API.md --output-json dist/docs-api.json && node ./scripts/combine_docs.js",
|
|
43
43
|
"build-sdk": "./scripts/build-native-sdks.sh",
|
|
44
44
|
"build": "npm run clean && npm run docgen && npm run docgen-api && tsc && rollup -c rollup.config.js && npm run build-sdk && ./scripts/copy-native-sdks.sh && npm pack && mv ./capacitor-background-runner-*.tgz ../../ && ./scripts/copy-readme.sh",
|
|
45
|
-
"pack": "npm run build &&
|
|
45
|
+
"pack": "npm run build && ./scripts/copy-native-sdks.sh && npm pack && mv ./capacitor-background-runner-*.tgz ../../",
|
|
46
46
|
"clean": "rimraf ./dist",
|
|
47
47
|
"watch": "tsc --watch",
|
|
48
48
|
"prepublishOnly": "npm run build",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"src": "android"
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "18f72b6c17bb83bb0e9dde3b0b4c1d3db5309b0d"
|
|
85
85
|
}
|
package/scripts/install_libs.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
const msgPrefix = '[@capacitor/background-runner]';
|
|
5
|
+
|
|
4
6
|
let workingDir = process.env.INIT_CWD;
|
|
5
7
|
if (!workingDir) {
|
|
6
8
|
workingDir = '.';
|
|
@@ -8,23 +10,43 @@ if (!workingDir) {
|
|
|
8
10
|
|
|
9
11
|
if (!fs.existsSync(path.join(workingDir, 'android'))) {
|
|
10
12
|
console.warn(
|
|
11
|
-
|
|
13
|
+
`${msgPrefix} cannot install android-js-engine library: @capacitor/android not installed, or in wrong parent directory.`,
|
|
12
14
|
);
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
)
|
|
18
|
+
const androidGradlePath = path.join(workingDir, 'android/app/build.gradle');
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(androidGradlePath)) {
|
|
21
|
+
console.warn(`${msgPrefix} android build.gradle file not found`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
19
24
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const searchText =
|
|
26
|
+
"dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'";
|
|
27
|
+
const insertText =
|
|
28
|
+
"\n\t\tdirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'";
|
|
29
|
+
|
|
30
|
+
let gradleFile = fs.readFileSync(androidGradlePath).toString('utf-8');
|
|
31
|
+
if (gradleFile.indexOf(insertText) != -1) {
|
|
32
|
+
return;
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
let insertIndex = gradleFile.indexOf(searchText) + searchText.length;
|
|
36
|
+
gradleFile =
|
|
37
|
+
gradleFile.substring(0, insertIndex) +
|
|
38
|
+
insertText +
|
|
39
|
+
gradleFile.substring(insertIndex);
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(androidGradlePath, gradleFile);
|
|
42
|
+
|
|
43
|
+
// remove old version of aar
|
|
44
|
+
const oldReleaseAARPath = path.join(
|
|
45
|
+
workingDir,
|
|
46
|
+
'android/src/main/libs/android-js-engine-release.aar',
|
|
28
47
|
);
|
|
48
|
+
if (fs.existsSync(oldReleaseAARPath)) {
|
|
49
|
+
fs.rmSync(oldReleaseAARPath);
|
|
50
|
+
}
|
|
29
51
|
|
|
30
|
-
console.log(
|
|
52
|
+
console.log(`${msgPrefix} installed lib path to gradle repositories`);
|