@capacitor/background-runner 1.0.1-dev-20230807T230144.0 → 1.0.1
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 +66 -53
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt +26 -6
- package/android/src/main/libs/android-js-engine-release.aar +0 -0
- package/ios/Plugin/BackgroundRunner.swift +47 -9
- package/ios/Plugin/CapacitorAPI/KV.swift +8 -3
- package/ios/Plugin/RunnerEngine/Context.swift +63 -35
- package/ios/Plugin/RunnerEngine/JSResponse.swift +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,13 +10,14 @@ npx cap sync
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Background Runner has support for various device APIs that require permission from the user prior to use.
|
|
13
|
+
|
|
13
14
|
## iOS
|
|
14
15
|
|
|
15
|
-
On iOS you must enable the Background Modes capability.
|
|
16
|
+
On iOS you must enable the Background Modes capability.
|
|
16
17
|
|
|
17
18
|

|
|
18
19
|
|
|
19
|
-
Once added, you must enable the `Background fetch` and `Background processing` modes at a minimum to enable the ability to register and schedule your background tasks.
|
|
20
|
+
Once added, you must enable the `Background fetch` and `Background processing` modes at a minimum to enable the ability to register and schedule your background tasks.
|
|
20
21
|
|
|
21
22
|
If you will be making use of Geolocation or Push Notifications, enable `Location updates` or `Remote notifications` respectively.
|
|
22
23
|
|
|
@@ -26,7 +27,7 @@ After enabling the Background Modes capability, add the following to your app's
|
|
|
26
27
|
|
|
27
28
|
```swift
|
|
28
29
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
// ....
|
|
31
32
|
BackgroundRunnerPlugin.registerBackgroundTask()
|
|
32
33
|
BackgroundRunnerPlugin.handleApplicationDidFinishLaunching(launchOptions: launchOptions)
|
|
@@ -37,6 +38,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
|
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
To allow the Background Runner to handle remote notifications, add the following:
|
|
41
|
+
|
|
40
42
|
```swift
|
|
41
43
|
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
42
44
|
// ....
|
|
@@ -52,6 +54,7 @@ func application(_ application: UIApplication, didReceiveRemoteNotification user
|
|
|
52
54
|
```
|
|
53
55
|
|
|
54
56
|
### Geolocation
|
|
57
|
+
|
|
55
58
|
Apple requires privacy descriptions to be specified in `Info.plist` for location information:
|
|
56
59
|
|
|
57
60
|
- `NSLocationAlwaysUsageDescription` (`Privacy - Location Always Usage Description`)
|
|
@@ -62,6 +65,7 @@ Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configura
|
|
|
62
65
|
## Android
|
|
63
66
|
|
|
64
67
|
### Geolocation
|
|
68
|
+
|
|
65
69
|
This API requires the following permissions be added to your `AndroidManifest.xml`:
|
|
66
70
|
|
|
67
71
|
```xml
|
|
@@ -74,7 +78,8 @@ This API requires the following permissions be added to your `AndroidManifest.xm
|
|
|
74
78
|
The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app _requires_ GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware.
|
|
75
79
|
|
|
76
80
|
### Local Notifications
|
|
77
|
-
|
|
81
|
+
|
|
82
|
+
Android 13 requires a permission check in order to send notifications. You are required to call `checkPermissions()` and `requestPermissions()` accordingly.
|
|
78
83
|
|
|
79
84
|
On Android 12 and older it won't show a prompt and will just return as granted.
|
|
80
85
|
|
|
@@ -89,62 +94,71 @@ Note that even if the permission is present, users can still disable exact notif
|
|
|
89
94
|
Read about [Setting Permissions](https://capacitorjs.com/docs/android/configuration#setting-permissions) in the [Android Guide](https://capacitorjs.com/docs/android) for more information on setting Android permissions.
|
|
90
95
|
|
|
91
96
|
## Using Background Runner
|
|
92
|
-
|
|
97
|
+
|
|
98
|
+
Background Runner is an event based JavaScript environment that emits events to a javascript runner file that you designate in your `capacitor.config.ts` file. If the runner finds a event handler corresponding to incoming event in your runner file, it will execute the event handler, then shutdown once `resolve()` or `reject()` are called (or if the OS force kills your process).
|
|
93
99
|
|
|
94
100
|
#### Example Runner JS File
|
|
95
101
|
|
|
96
102
|
```js
|
|
97
|
-
addEventListener(
|
|
98
|
-
console.log(
|
|
99
|
-
|
|
103
|
+
addEventListener('myCustomEvent', (resolve, reject, args) => {
|
|
104
|
+
console.log('do something to update the system here');
|
|
105
|
+
resolve();
|
|
100
106
|
});
|
|
101
107
|
|
|
102
|
-
addEventListener(
|
|
103
|
-
|
|
108
|
+
addEventListener('myCustomEventWithReturnData', (resolve, reject, args) => {
|
|
109
|
+
try {
|
|
110
|
+
console.log('accepted this data: ' + JSON.stringify(args.user));
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
const updatedUser = args.user;
|
|
113
|
+
updatedUser.firstName = updatedUser.firstName + ' HELLO';
|
|
114
|
+
updatedUser.lastName = updatedUser.lastName + ' WORLD';
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
resolve(updatedUser);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
reject(err);
|
|
119
|
+
}
|
|
110
120
|
});
|
|
111
121
|
|
|
112
|
-
addEventListener(
|
|
113
|
-
|
|
122
|
+
addEventListener('remoteNotification', (resolve, reject, args) => {
|
|
123
|
+
try {
|
|
124
|
+
console.log('received silent push notification');
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
CapacitorNotifications.schedule([
|
|
127
|
+
{
|
|
117
128
|
id: 100,
|
|
118
|
-
title:
|
|
119
|
-
body:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
title: 'Enterprise Background Runner',
|
|
130
|
+
body: 'Received silent push notification',
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
resolve();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
reject();
|
|
137
|
+
}
|
|
124
138
|
});
|
|
125
|
-
|
|
126
139
|
```
|
|
127
|
-
|
|
140
|
+
|
|
141
|
+
Calling `resolve()` \ `reject()` is **required** within every event handler called by the runner. Failure to do this could result in your runner being killed by the OS if your event is called while the app is in the background. If the app is in the foreground, async calls to `dispatchEvent` may not resolve.
|
|
128
142
|
|
|
129
143
|
## Configuring Background Runner
|
|
130
|
-
|
|
144
|
+
|
|
145
|
+
On load, Background Runner will automatically register a background task that will be scheduled and ran once your app is backgrounded. The settings for this behavior is defined in your `capacitor.config.ts` file:
|
|
131
146
|
|
|
132
147
|
```ts
|
|
133
148
|
const config: CapacitorConfig = {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
149
|
+
plugins: {
|
|
150
|
+
BackgroundRunner: {
|
|
151
|
+
label: 'com.example.background.task',
|
|
152
|
+
src: 'background.js',
|
|
153
|
+
event: 'myCustomEvent',
|
|
154
|
+
repeat: true,
|
|
155
|
+
interval: 2,
|
|
156
|
+
autoStart: false,
|
|
143
157
|
},
|
|
144
|
-
}
|
|
158
|
+
},
|
|
159
|
+
};
|
|
145
160
|
```
|
|
146
161
|
|
|
147
|
-
|
|
148
162
|
## JavaScript API
|
|
149
163
|
|
|
150
164
|
Background Runner does not execute your Javascript code in a browser or web view, therefore the typical Web APIs you may be used to may not be available. This includes DOM APIs nor ability to interact with your application's DOM.
|
|
@@ -152,48 +166,47 @@ Background Runner does not execute your Javascript code in a browser or web view
|
|
|
152
166
|
Below is a list of the available Web APIs provided in Background Runner:
|
|
153
167
|
|
|
154
168
|
- [console](https://developer.mozilla.org/en-US/docs/Web/API/console)
|
|
155
|
-
|
|
169
|
+
- only `info`, `log`, `warn`, `error` , and `debug` are available
|
|
156
170
|
- [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)
|
|
157
|
-
|
|
171
|
+
- only `decode` is available
|
|
158
172
|
- [TextEncoder](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder)
|
|
159
|
-
|
|
173
|
+
- only `encode` is available
|
|
160
174
|
- [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
|
|
161
|
-
|
|
175
|
+
- Event Listener options and `useCapture` not supported
|
|
162
176
|
- [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout)
|
|
163
177
|
- [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)
|
|
164
178
|
- [clearTimeout](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)
|
|
165
179
|
- [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)
|
|
166
180
|
- [crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
|
|
167
181
|
- [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
- Request object not yet supported
|
|
183
|
+
- Only `method`, `headers` and `body` supported in options object
|
|
170
184
|
|
|
171
185
|
In addition to the standard Web APIs, Background Runner also supports a number of [custom Capacitor APIs](#capacitor-api) custom APIs that expose relevant mobile device functionality
|
|
172
186
|
|
|
173
|
-
|
|
174
|
-
|
|
175
187
|
## Runner Lifetimes
|
|
176
188
|
|
|
177
|
-
Currently, the runners are designed for performing periodic bursts of work while your app is in the background, or for executing asynchronous work in a separate
|
|
189
|
+
Currently, the runners are designed for performing periodic bursts of work while your app is in the background, or for executing asynchronous work in a thread separate from your UI while your app is in the foreground. As a result, runners are not long lived. State is not maintained between calls to events in the runner. Each call to `dispatchEvent()` creates a new context in which your runner code is loaded and executed, and once `resolve()` or `reject()` is called, the context is destroyed.
|
|
178
190
|
|
|
179
191
|
## Android Battery Optimizations
|
|
180
|
-
|
|
192
|
+
|
|
193
|
+
Some Android vendors offer built-in battery optimization settings that go beyond what stock Android provides. Some of these optimizations must be disabled by your end users in order for your background tasks to work properly.
|
|
181
194
|
|
|
182
195
|
Visit [Don't kill my app!](https://dontkillmyapp.com) for more information on the affected manufacturers and steps required by your users to adjust the settings.
|
|
183
196
|
|
|
184
197
|
## Limitations of Background Tasks
|
|
185
198
|
|
|
186
|
-
It’s not possible to run persistent, always running background services on mobile operating systems.
|
|
199
|
+
It’s not possible to run persistent, always running background services on mobile operating systems. Due to the limitations imposed by iOS and Android designed to reduce battery and data consumption, background tasks are constrained with various limitations that you must keep in mind while designing and implementing your background task.
|
|
187
200
|
|
|
188
201
|
### iOS
|
|
189
202
|
|
|
190
203
|
- Each invocation of your task has approximately up to 30 seconds of runtime before you must call `completed()` or your task is killed.
|
|
191
|
-
- While you can set an interval to define when your task runs after the app is backgrounded, or how often it should run, this is not guaranteed.
|
|
204
|
+
- While you can set an interval to define when your task runs after the app is backgrounded, or how often it should run, this is not guaranteed. iOS will determine when and how often you task will ultimately run, determined in part by how often you app is used.
|
|
192
205
|
|
|
193
206
|
### Android
|
|
194
207
|
|
|
195
208
|
- Your task has a maximum of 10 minutes to perform work, but to keep your task cross platform compatible, you should limit your work to 30 seconds at most.
|
|
196
|
-
- Repeating background tasks have a minimal interval of at least 15 minutes.
|
|
209
|
+
- Repeating background tasks have a minimal interval of at least 15 minutes. Similar to iOS, any interval you request may not be hit exactly - actual execution time is subject to OS battery optimizations and other heuristics.
|
|
197
210
|
|
|
198
211
|
## API
|
|
199
212
|
|
|
@@ -304,8 +317,8 @@ Dispatches an event to the configured runner.
|
|
|
304
317
|
|
|
305
318
|
</docgen-api>
|
|
306
319
|
|
|
307
|
-
|
|
308
320
|
## Capacitor API
|
|
321
|
+
|
|
309
322
|
<capacitor-api-docs>
|
|
310
323
|
|
|
311
324
|
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
@@ -404,4 +417,4 @@ Get access to device location information.
|
|
|
404
417
|
| **`speed`** | <code>number \| null</code> | The speed the user is traveling (if available) | 1.0.0 |
|
|
405
418
|
| **`heading`** | <code>number \| null</code> | The heading the user is facing (if available) | 1.0.0 |
|
|
406
419
|
|
|
407
|
-
</capacitor-api-docs>
|
|
420
|
+
</capacitor-api-docs>
|
|
@@ -50,6 +50,7 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
50
50
|
|
|
51
51
|
runner = Runner()
|
|
52
52
|
context = initContext(config, androidContext)
|
|
53
|
+
context?.start()
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
fun scheduleBackgroundTask(androidContext: android.content.Context) {
|
|
@@ -84,7 +85,7 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
suspend fun execute(androidContext: android.content.Context, config: RunnerConfig,
|
|
88
|
+
suspend fun execute(androidContext: android.content.Context, config: RunnerConfig, dataArgs: JSONObject = JSONObject()): JSONObject? {
|
|
88
89
|
if (!started) {
|
|
89
90
|
start(androidContext)
|
|
90
91
|
}
|
|
@@ -93,17 +94,37 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
93
94
|
|
|
94
95
|
val future = MutableStateFlow<Result<JSONObject?>?>(null)
|
|
95
96
|
|
|
96
|
-
class
|
|
97
|
+
class ResolveCallback : JSFunction(jsName = "resolve") {
|
|
97
98
|
override fun run() {
|
|
98
99
|
super.run()
|
|
99
100
|
future.value = Result.success(this.args)
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
class RejectCallback : JSFunction(jsName = "reject") {
|
|
105
|
+
override fun run() {
|
|
106
|
+
super.run()
|
|
107
|
+
val rejectionTitle = this.args?.optString("name", "Error") ?: "Error"
|
|
108
|
+
val rejectionMessage = this.args?.optString("message") ?: ""
|
|
109
|
+
val error = Exception("$rejectionTitle: $rejectionMessage")
|
|
110
|
+
|
|
111
|
+
future.value = Result.failure(error)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
105
114
|
|
|
106
|
-
|
|
115
|
+
val resolve = ResolveCallback()
|
|
116
|
+
val reject = RejectCallback()
|
|
117
|
+
context!!.registerFunction("_resolveCallback", resolve)
|
|
118
|
+
context!!.registerFunction("_rejectCallback", reject)
|
|
119
|
+
|
|
120
|
+
val args = JSONObject()
|
|
121
|
+
|
|
122
|
+
val callbacks = JSONObject();
|
|
123
|
+
callbacks.put("resolve", "__cbr::_resolveCallback")
|
|
124
|
+
callbacks.put("reject", "__cbr::_rejectCallback")
|
|
125
|
+
|
|
126
|
+
args.put("callbacks", callbacks)
|
|
127
|
+
args.put("dataArgs", dataArgs)
|
|
107
128
|
|
|
108
129
|
context!!.dispatchEvent(config.event, args)
|
|
109
130
|
|
|
@@ -158,7 +179,6 @@ class BackgroundRunner(context: android.content.Context) {
|
|
|
158
179
|
|
|
159
180
|
newContext.setCapacitorAPI(api)
|
|
160
181
|
|
|
161
|
-
|
|
162
182
|
newContext.execute(srcFile, false)
|
|
163
183
|
|
|
164
184
|
return newContext
|
|
Binary file
|
|
@@ -145,17 +145,12 @@ public class BackgroundRunner {
|
|
|
145
145
|
throw BackgroundRunnerPluginError.runnerError(reason: "no loaded context for config")
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
var inputArgsWithCallback = inputArgs
|
|
149
|
-
|
|
150
|
-
if inputArgsWithCallback == nil {
|
|
151
|
-
inputArgsWithCallback = [:]
|
|
152
|
-
}
|
|
153
|
-
|
|
154
148
|
let waitGroup = DispatchGroup()
|
|
155
149
|
|
|
156
150
|
var result: [String: Any]?
|
|
151
|
+
var rejectionErr: JSError?
|
|
157
152
|
|
|
158
|
-
let
|
|
153
|
+
let resolveFunc: @convention(block)(JavaScriptCore.JSValue) -> Void = { returnObj in
|
|
159
154
|
if !returnObj.isUndefined && !returnObj.isNull {
|
|
160
155
|
var dict: [String: Any] = [:]
|
|
161
156
|
|
|
@@ -170,14 +165,57 @@ public class BackgroundRunner {
|
|
|
170
165
|
return
|
|
171
166
|
}
|
|
172
167
|
|
|
173
|
-
|
|
168
|
+
let rejectFunc: @convention(block)(JavaScriptCore.JSValue) -> Void = { rejectObj in
|
|
169
|
+
var rejectionTitle = "Error"
|
|
170
|
+
var rejectionMessage = ""
|
|
171
|
+
|
|
172
|
+
if !rejectObj.isUndefined && !rejectObj.isNull {
|
|
173
|
+
if let errStr = rejectObj.toString() {
|
|
174
|
+
let split = errStr.split(separator: ":", maxSplits: 1)
|
|
175
|
+
if split.count == 0 {
|
|
176
|
+
rejectionMessage = String(split.first ?? "")
|
|
177
|
+
} else {
|
|
178
|
+
rejectionTitle = String(split.first ?? "Error")
|
|
179
|
+
rejectionMessage = String(split.last ?? "")
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if rejectObj.isObject, let errDict = rejectObj.toDictionary() {
|
|
184
|
+
if let errTitle = errDict["name"] as? String {
|
|
185
|
+
rejectionTitle = errTitle
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if let errMsg = errDict["message"] as? String {
|
|
189
|
+
rejectionMessage = errMsg
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
rejectionErr = JSError(message: "\(rejectionTitle): \(rejectionMessage)")
|
|
195
|
+
|
|
196
|
+
waitGroup.leave()
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
var args: [String: Any] = [:]
|
|
201
|
+
|
|
202
|
+
var callbacks: [String: Any] = [:]
|
|
203
|
+
callbacks["__cbr::resolve"] = resolveFunc
|
|
204
|
+
callbacks["__cbr::reject"] = rejectFunc
|
|
205
|
+
|
|
206
|
+
args["callbacks"] = callbacks
|
|
207
|
+
args["dataArgs"] = inputArgs
|
|
174
208
|
|
|
175
209
|
waitGroup.enter()
|
|
176
210
|
|
|
177
|
-
try context.dispatchEvent(event: event, details:
|
|
211
|
+
try context.dispatchEvent(event: event, details: args)
|
|
178
212
|
|
|
179
213
|
waitGroup.wait()
|
|
180
214
|
|
|
215
|
+
if let rejection = rejectionErr {
|
|
216
|
+
throw EngineError.jsException(details: rejection.message)
|
|
217
|
+
}
|
|
218
|
+
|
|
181
219
|
return result
|
|
182
220
|
} catch {
|
|
183
221
|
print("error executing task: \(error)")
|
|
@@ -3,7 +3,7 @@ import JavaScriptCore
|
|
|
3
3
|
|
|
4
4
|
@objc protocol CapacitorKVStoreExports: JSExport {
|
|
5
5
|
static func set(_ key: String, _ value: String)
|
|
6
|
-
static func get(_ key: String) ->
|
|
6
|
+
static func get(_ key: String) -> JSValue
|
|
7
7
|
static func remove(_ key: String)
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -12,8 +12,13 @@ class CapacitorKVStore: NSObject, CapacitorKVStoreExports {
|
|
|
12
12
|
UserDefaults.standard.set(value, forKey: key)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
static func get(_ key: String) ->
|
|
16
|
-
|
|
15
|
+
static func get(_ key: String) -> JSValue {
|
|
16
|
+
var valueWrapper: [String: String] = [:]
|
|
17
|
+
|
|
18
|
+
let value = UserDefaults.standard.string(forKey: key)
|
|
19
|
+
valueWrapper["value"] = value
|
|
20
|
+
|
|
21
|
+
return JSValue(object: valueWrapper, in: JSContext.current())
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
static func remove(_ key: String) {
|
|
@@ -6,7 +6,9 @@ public class Context {
|
|
|
6
6
|
let ctx: JSContext
|
|
7
7
|
|
|
8
8
|
private var timers = [Int: Timer]()
|
|
9
|
-
private var eventListeners = [String:
|
|
9
|
+
private var eventListeners = [String: JSValue]()
|
|
10
|
+
private var thread: Thread!
|
|
11
|
+
private var runLoop: RunLoop?
|
|
10
12
|
|
|
11
13
|
// swiftlint:disable:next identifier_name
|
|
12
14
|
public init(vm: JSVirtualMachine, ctxName: String) throws {
|
|
@@ -19,6 +21,20 @@ public class Context {
|
|
|
19
21
|
name = ctxName
|
|
20
22
|
ctx = newCtx
|
|
21
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()
|
|
37
|
+
|
|
22
38
|
try setupWebAPI()
|
|
23
39
|
}
|
|
24
40
|
|
|
@@ -39,41 +55,51 @@ public class Context {
|
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
public func dispatchEvent(event: String, details: [String: Any]? = nil) throws {
|
|
42
|
-
if let
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
if let eventHandler = eventListeners[event] {
|
|
59
|
+
var dataArgs = details?["dataArgs"] as? [String: Any]
|
|
60
|
+
if dataArgs == nil {
|
|
61
|
+
dataArgs = details
|
|
62
|
+
}
|
|
45
63
|
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
var callbackFunctions: [String: JSValue] = [:]
|
|
65
|
+
|
|
66
|
+
if let callbacks = details?["callbacks"] as? [String: Any] {
|
|
67
|
+
callbacks.keys.forEach { key in
|
|
68
|
+
if key.hasPrefix("__cbr::") {
|
|
69
|
+
let funcName = key.replacingOccurrences(of: "__cbr::", with: "")
|
|
70
|
+
callbackFunctions[funcName] = JSValue.init(object: callbacks[key], in: self.ctx)
|
|
71
|
+
}
|
|
48
72
|
}
|
|
73
|
+
}
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
var callbackFunctions: [String: Any] = [:]
|
|
75
|
+
var thrownException: JSValue?
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
callbackFunctions[funcName] = detailsObj[key]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
77
|
+
self.ctx.exceptionHandler = { _, exception in
|
|
78
|
+
thrownException = exception
|
|
79
|
+
}
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
throw EngineError.jsValueError
|
|
62
|
-
}
|
|
81
|
+
var callArgs: [Any] = []
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
if let resolveFunc = callbackFunctions["resolve"],
|
|
84
|
+
let rejectFunc = callbackFunctions["reject"] {
|
|
85
|
+
callArgs.append(resolveFunc)
|
|
86
|
+
callArgs.append(rejectFunc)
|
|
87
|
+
}
|
|
67
88
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
if let dataArgs = dataArgs {
|
|
90
|
+
guard let jsDataArgs = JSValue(object: dataArgs, in: self.ctx) else {
|
|
91
|
+
throw EngineError.jsValueError
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
throw EngineError.jsException(details: String(describing: exception))
|
|
75
|
-
}
|
|
94
|
+
callArgs.append(jsDataArgs)
|
|
76
95
|
}
|
|
96
|
+
|
|
97
|
+
eventHandler.call(withArguments: callArgs)
|
|
98
|
+
|
|
99
|
+
if let exception = thrownException {
|
|
100
|
+
throw EngineError.jsException(details: String(describing: exception))
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
}
|
|
78
104
|
}
|
|
79
105
|
private func setupWebAPI() throws {
|
|
@@ -93,6 +119,7 @@ public class Context {
|
|
|
93
119
|
let fetchFunc: @convention(block) (JSValue, JSValue) -> JSValue = { resource, options in
|
|
94
120
|
return fetch(resource: resource, options: options)
|
|
95
121
|
}
|
|
122
|
+
|
|
96
123
|
let newTextEncoderConst: @convention(block) () -> JSTextEncoder = JSTextEncoder.init
|
|
97
124
|
let newTextDecoderConst: @convention(block) (String?, [AnyHashable: Any]?) -> JSTextDecoder = JSTextDecoder.init
|
|
98
125
|
|
|
@@ -110,15 +137,11 @@ public class Context {
|
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
private func addEventListener(eventName: String, callback: JSValue) {
|
|
113
|
-
|
|
114
|
-
eventListeners[eventName] = [callback]
|
|
115
|
-
} else {
|
|
116
|
-
eventListeners[eventName]?.append(callback)
|
|
117
|
-
}
|
|
140
|
+
eventListeners[eventName] = callback
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
private func initTimer(callback: JSValue, timeout: Int, isInterval: Bool) -> Int {
|
|
121
|
-
let timer = Timer
|
|
144
|
+
let timer = Timer(timeInterval: TimeInterval(timeout / 1000), repeats: isInterval) { t in
|
|
122
145
|
callback.call(withArguments: [])
|
|
123
146
|
if !isInterval {
|
|
124
147
|
let timerId = Int(Int32.init(truncatingIfNeeded: t.hashValue))
|
|
@@ -127,11 +150,16 @@ public class Context {
|
|
|
127
150
|
}
|
|
128
151
|
|
|
129
152
|
let timerId = Int(Int32.init(truncatingIfNeeded: timer.hashValue))
|
|
130
|
-
|
|
131
|
-
RunLoop.current.add(timer, forMode: .common)
|
|
132
|
-
|
|
133
153
|
timers[timerId] = timer
|
|
134
154
|
|
|
155
|
+
if let runLoop = self.runLoop {
|
|
156
|
+
runLoop.add(timer, forMode: .default)
|
|
157
|
+
runLoop.add(timer, forMode: .common)
|
|
158
|
+
} else {
|
|
159
|
+
RunLoop.current.add(timer, forMode: .default)
|
|
160
|
+
RunLoop.current.add(timer, forMode: .common)
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
return timerId
|
|
136
164
|
}
|
|
137
165
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor/background-runner",
|
|
3
|
-
"version": "1.0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Capacitor Background Runner",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"src": "android"
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "737db866d360fc2717724ddfa470ba0aad729ab6"
|
|
84
84
|
}
|