@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.
Files changed (33) hide show
  1. package/README.md +3 -2
  2. package/android/build.gradle +4 -1
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt +67 -51
  5. package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +30 -14
  6. package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerConfig.kt +28 -23
  7. package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerWorker.kt +8 -2
  8. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +1 -2
  9. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Geolocation.kt +1 -1
  10. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/KV.kt +1 -1
  11. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +4 -2
  12. package/android/src/main/libs/android-js-engine-release.aar +0 -0
  13. package/dist/docs-api.json +1 -1
  14. package/dist/docs.json +3 -2
  15. package/dist/esm/apis.d.ts +1 -1
  16. package/dist/esm/apis.js.map +1 -1
  17. package/dist/esm/definitions.d.ts +1 -1
  18. package/dist/esm/definitions.js.map +1 -1
  19. package/dist/esm/web.d.ts +2 -2
  20. package/dist/esm/web.js +1 -1
  21. package/dist/esm/web.js.map +1 -1
  22. package/dist/plugin.cjs.js +1 -1
  23. package/dist/plugin.cjs.js.map +1 -1
  24. package/dist/plugin.js +1 -1
  25. package/dist/plugin.js.map +1 -1
  26. package/ios/Plugin/BackgroundRunner.swift +13 -52
  27. package/ios/Plugin/BackgroundRunnerPlugin.swift +2 -1
  28. package/ios/Plugin/Context+CapacitorAPI.swift +5 -5
  29. package/ios/Plugin/RunnerEngine/Context.swift +27 -44
  30. package/ios/Plugin/RunnerEngine/Errors.swift +1 -0
  31. package/ios/Plugin/RunnerEngine/Runner.swift +43 -3
  32. package/package.json +3 -3
  33. 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
- | **`getCurrentLocation`** | <code>() =&gt; <a href="#getcurrentpositionresult">GetCurrentPositionResult</a></code> | Get the device's last known location | 1.0.0 |
459
+ | **`getCurrentPosition`** | <code>() =&gt; <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; }) =&gt; void</code> | Replaces the current UI on the watch with what is specified here. |
489
489
  | **`updateWatchData`** | <code>(options: { data: { [key: string]: string; }; }) =&gt; 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>
@@ -52,7 +52,10 @@ android {
52
52
  repositories {
53
53
  google()
54
54
  mavenCentral()
55
- flatDir { dirs "./src/main/libs" }
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.JSFunction
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
- public val config: RunnerConfig?
28
- private var runner: Runner? = null
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 start(androidContext: android.content.Context) {
49
- config ?: throw Exception("...no runner config to start")
41
+ fun shutdown() {
42
+ val runner = runner ?: return
50
43
 
51
- runner = Runner()
52
- context = initContext(config, androidContext)
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
- if (!started) {
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 ?: throw Exception("no loaded context for config")
89
+ val context = initContext(config, androidContext, callbackId)
94
90
 
95
- val future = MutableStateFlow<Result<JSONObject?>?>(null)
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.args)
100
+ future.value = Result.success(this.jsFunctionArgs)
101
101
  }
102
102
  }
103
103
 
104
- class RejectCallback : JSFunction(jsName = "reject") {
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.args?.optString("name", "Error") ?: "Error"
108
- val rejectionMessage = this.args?.optString("message") ?: ""
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 resolve = ResolveCallback()
116
- val reject = RejectCallback()
117
- context!!.registerFunction("_resolveCallback", resolve)
118
- context!!.registerFunction("_rejectCallback", reject)
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!!.dispatchEvent(config.event, args)
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
- runner?.destroy()
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 initialized")
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
- val newContext = runner!!.createContext(config.label)
180
+ var contextName = config.label
181
+ if (callbackId != null) {
182
+ contextName += "-$callbackId"
183
+ }
173
184
 
174
- val api = CapacitorAPI(config.label)
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.setCapacitorAPI(api)
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.getInstance(this.context)
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 = BackgroundRunner.getInstance(this.context)
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
- GlobalScope.launch(Dispatchers.Default) {
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
- config,
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(configObject: JSONObject) {
7
- val label: String
8
- val src: String
9
- val autoSchedule: Boolean
10
- val repeats: Boolean
11
- var event: String
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
- label = labelStr
26
- src = srcStr
27
- autoSchedule = configObject.optBoolean("autoStart", false)
28
- event = configObject.optString("event")
29
- repeats = configObject.optBoolean("repeat")
30
- interval = configObject.optInt("interval", 0)
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.getInstance(this@RunnerWorker.applicationContext)
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.api.DeviceAPI
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.api.GeolocationAPI
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.api.KVAPI
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.Logger.config
16
+ import com.getcapacitor.CapConfig
17
17
  import com.getcapacitor.plugin.util.AssetUtil
18
- import io.ionic.android_js_engine.api.NotificationsAPI
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
  }
@@ -482,7 +482,7 @@
482
482
  "methods": [],
483
483
  "properties": [
484
484
  {
485
- "name": "getCurrentLocation",
485
+ "name": "getCurrentPosition",
486
486
  "tags": [
487
487
  {
488
488
  "text": "1.0.0",
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
  }
@@ -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
- getCurrentLocation: () => GetCurrentPositionResult;
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.
@@ -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 getCurrentLocation: () => 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"]}
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<void>;
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<void>;\n}\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n BackgroundRunner?: BackgroundRunnerConfig;\n }\n}\n"]}
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<void>;
7
+ dispatchEvent<T = void>(_options: DispatchEventOptions): Promise<T>;
8
8
  }
package/dist/esm/web.js CHANGED
@@ -9,7 +9,7 @@ export class BackgroundRunnerWeb extends WebPlugin {
9
9
  registerBackgroundTask() {
10
10
  throw new Error('not available on web');
11
11
  }
12
- dispatchEvent() {
12
+ dispatchEvent(_options) {
13
13
  throw new Error('not available on web');
14
14
  }
15
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,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;QACX,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { BackgroundRunnerPlugin, PermissionStatus } 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(): Promise<void> {\n throw new Error('not available on web');\n }\n}\n"]}
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"]}
@@ -18,7 +18,7 @@ class BackgroundRunnerWeb extends core.WebPlugin {
18
18
  registerBackgroundTask() {
19
19
  throw new Error('not available on web');
20
20
  }
21
- dispatchEvent() {
21
+ dispatchEvent(_options) {
22
22
  throw new Error('not available on web');
23
23
  }
24
24
  }
@@ -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,GAAG;AACpB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAChD,KAAK;AACL;;;;;;;;;"}
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
@@ -15,7 +15,7 @@ var capacitorBackgroundRunner = (function (exports, core) {
15
15
  registerBackgroundTask() {
16
16
  throw new Error('not available on web');
17
17
  }
18
- dispatchEvent() {
18
+ dispatchEvent(_options) {
19
19
  throw new Error('not available on web');
20
20
  }
21
21
  }
@@ -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,GAAG;IACpB,QAAQ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAChD,KAAK;IACL;;;;;;;;;;;;;;;;;"}
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
- if !started {
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: config.label)
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
- ctx.setObject(CapacitorKVStore.self, forKeyedSubscript: "CapacitorKV" as NSString)
6
- ctx.setObject(CapacitorGeolocation(context: self), forKeyedSubscript: "CapacitorGeolocation" as NSString)
7
- ctx.setObject(CapacitorWatch(), forKeyedSubscript: "CapacitorWatch" as NSString)
8
- ctx.setObject(CapacitorNotifications.self, forKeyedSubscript: "CapacitorNotifications" as NSString)
9
- ctx.setObject(CapacitorDevice(), forKeyedSubscript: "CapacitorDevice" as NSString)
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 ctx: JSContext
6
+ let jsContext: JSContext
7
7
 
8
- private var timers = [Int: Timer]()
9
8
  private var eventListeners = [String: JSValue]()
10
- private var thread: Thread!
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, ctxName: String) throws {
15
- guard let newCtx = JSContext(virtualMachine: vm) else {
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
- newCtx.name = ctxName
20
-
21
- name = ctxName
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
- ctx.exceptionHandler = { _, exception in
28
+ jsContext.exceptionHandler = { _, exception in
45
29
  thrownException = exception
46
30
  }
47
31
 
48
- let value = ctx.evaluateScript(code)
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.ctx)
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.ctx.exceptionHandler = { _, exception in
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.ctx) else {
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
- ctx.setObject(consoleObj, forKeyedSubscript: "console" as NSString)
127
- ctx.setObject(fetchFunc, forKeyedSubscript: "fetch" as NSString)
128
- ctx.setObject(JSCrypto.self, forKeyedSubscript: "crypto" as NSString)
129
- ctx.setObject(addEventListenerFunc, forKeyedSubscript: "addEventListener" as NSString)
130
- ctx.setObject(setTimeoutFunc, forKeyedSubscript: "setTimeout" as NSString)
131
- ctx.setObject(setIntervalFunc, forKeyedSubscript: "setInterval" as NSString)
132
- ctx.setObject(clearTimeoutFunc, forKeyedSubscript: "clearTimeout" as NSString)
133
- ctx.setObject(clearTimeoutFunc, forKeyedSubscript: "clearInterval" as NSString)
134
- ctx.setObject(newTextEncoderConst, forKeyedSubscript: "TextEncoder" as NSString)
135
- ctx.setObject(newTextDecoderConst, forKeyedSubscript: "TextDecoder" as NSString)
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
@@ -5,4 +5,5 @@ public enum EngineError: Error {
5
5
  case jsValueError
6
6
  case sourceNotFound
7
7
  case jsException(details: String)
8
+ case runnerError(details: String)
8
9
  }
@@ -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 ctx = try Context(vm: machine, ctxName: name)
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] = ctx
24
+ contexts[name] = context
16
25
 
17
- return ctx
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-20230831T174057.0",
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 && && ./scripts/copy-native-sdks.sh && npm pack && mv ./capacitor-background-runner-*.tgz ../../",
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": "1f8633987278f10280966ca7a560211973d4104d"
84
+ "gitHead": "18f72b6c17bb83bb0e9dde3b0b4c1d3db5309b0d"
85
85
  }
@@ -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
- 'cannot install android-js-engine library: @capacitor/android not installed, or in wrong parent directory.',
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 releaseAARPath = path.join(
17
- 'android/src/main/libs/android-js-engine-release.aar',
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 fullPath = path.join(workingDir, 'android/app/libs');
21
- if (!fs.existsSync(fullPath)) {
22
- fs.mkdirSync(fullPath);
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
- fs.copyFileSync(
26
- releaseAARPath,
27
- path.join(fullPath, 'android-js-engine-release.aar'),
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(`copied android-js-engine-release.aar to ${fullPath}`);
52
+ console.log(`${msgPrefix} installed lib path to gradle repositories`);