@tokamakdev/plugin-location 0.1.0-beta.83 → 0.1.0-beta.84

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 CHANGED
@@ -25,6 +25,18 @@ milliseconds, 0 by default, or takes a new one, and rejects with
25
25
  `TimeoutError` when none arrives within `timeout` milliseconds, unlimited by
26
26
  default. The timeout starts once the user allows location.
27
27
 
28
+ ## Off screen
29
+
30
+ The plugin asks for location while the app is in use only. While the app is
31
+ off screen on iOS or Android, as when Worker code runs in the background, a
32
+ call that would ask the user for permission rejects with `NeedsUIError`. With
33
+ permission granted, the platforms stop locating an app that is off screen
34
+ without background location permission, which the plugin does not request, so
35
+ `getCurrentPosition` rejects with `NotAllowedError` there. iOS still locates
36
+ the app for a few seconds after it leaves the screen.
37
+
38
+ ## Platforms
39
+
28
40
  The web implementation uses `navigator.geolocation`. Native Tokamak builds use
29
41
  Core Location on Apple platforms, `LocationManager` on Android, and WebView2's
30
42
  location implementation on Windows. Calling the API on a platform without
@@ -59,7 +59,10 @@ class TokamakLocationPlugin(
59
59
  }
60
60
  }
61
61
 
62
- /** Passes [action] whether the app may read fine or coarse location, asking when it may not. */
62
+ /**
63
+ * Passes [action] whether the app may read fine or coarse location, asking when it may not.
64
+ * Throws `NeedsUIError` when asking would show UI while the app is in the background.
65
+ */
63
66
  private fun withPermission(action: (Boolean) -> Unit) {
64
67
  if (PERMISSIONS.any { context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED }) {
65
68
  action(true)
@@ -93,8 +96,7 @@ class TokamakLocationPlugin(
93
96
  context.mainExecutor,
94
97
  ) { location ->
95
98
  handler.removeCallbacks(timeout)
96
- if (location == null) reply(unavailable("Location is unavailable"))
97
- else reply(Result.success(position(location)))
99
+ reply(if (location == null) missingFix() else Result.success(position(location)))
98
100
  }
99
101
  }.onFailure {
100
102
  handler.removeCallbacks(timeout)
@@ -165,6 +167,14 @@ class TokamakLocationPlugin(
165
167
  private fun permissionDenied(): Result<Any?> =
166
168
  Result.failure(TokamakPluginError("NotAllowedError", "Location permission was denied"))
167
169
 
170
+ /** Why no fix came: Android gives none in the background without background location permission. */
171
+ private fun missingFix(): Result<Any?> =
172
+ if (host.isInForeground || context.checkSelfPermission(BACKGROUND) == PackageManager.PERMISSION_GRANTED) {
173
+ unavailable("Location is unavailable")
174
+ } else {
175
+ Result.failure(TokamakPluginError("NotAllowedError", "Location permission does not cover the background"))
176
+ }
177
+
168
178
  private fun unavailable(message: String): Result<Any?> =
169
179
  Result.failure(TokamakPluginError("NotReadableError", message))
170
180
 
@@ -176,5 +186,6 @@ class TokamakLocationPlugin(
176
186
  /** The `timeout` that never expires, as the page sends it. */
177
187
  const val UNLIMITED = 0xffffffffL
178
188
  val PERMISSIONS = setOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
189
+ const val BACKGROUND = Manifest.permission.ACCESS_BACKGROUND_LOCATION
179
190
  }
180
191
  }
@@ -23,6 +23,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
23
23
  var timer: DispatchWorkItem?
24
24
  }
25
25
 
26
+ private let host: TokamakHost
26
27
  /// Updates watchers.
27
28
  private let updates = CLLocationManager()
28
29
  /// Takes single fixes for current position requests, apart from watching.
@@ -31,6 +32,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
31
32
  private var watchers: [UUID: TokamakPluginReply] = [:]
32
33
 
33
34
  init(host: TokamakHost) {
35
+ self.host = host
34
36
  super.init()
35
37
  for manager in [updates, fixes] {
36
38
  manager.delegate = self
@@ -60,10 +62,9 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
60
62
  method: String,
61
63
  arguments: Any,
62
64
  reply: @escaping TokamakPluginReply
63
- ) -> (() -> Void) {
65
+ ) throws(TokamakPluginError) -> (() -> Void) {
64
66
  guard method == "watchPosition" else {
65
- reply(.failure(.notSupported("\(id).\(method) is not supported")))
66
- return {}
67
+ throw .notSupported("\(id).\(method) is not supported")
67
68
  }
68
69
  let subscription = UUID()
69
70
  watchers[subscription] = reply
@@ -121,10 +122,15 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
121
122
  return
122
123
  }
123
124
  let status = updates.authorizationStatus
124
- if status == .notDetermined {
125
- updates.requestWhenInUseAuthorization()
126
- } else {
125
+ guard status == .notDetermined else {
127
126
  serve(status)
127
+ return
128
+ }
129
+ do throws(TokamakPluginError) {
130
+ try host.requireUI()
131
+ updates.requestWhenInUseAuthorization()
132
+ } catch {
133
+ fail(error)
128
134
  }
129
135
  }
130
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.83",
3
+ "version": "0.1.0-beta.84",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -11,7 +11,7 @@
11
11
  ".": "./src/index.ts"
12
12
  },
13
13
  "peerDependencies": {
14
- "@tokamakdev/plugin": "0.1.0-beta.83"
14
+ "@tokamakdev/plugin": "0.1.0-beta.84"
15
15
  },
16
16
  "files": [
17
17
  "android",
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FrontendPlugin } from "@tokamakdev/plugin";
1
+ import { Plugin } from "@tokamakdev/plugin";
2
2
  import * as web from "../web/index.js";
3
3
 
4
4
  export interface Coordinates {
@@ -27,7 +27,7 @@ export interface PositionOptions {
27
27
  export type PositionCallback = (position: Position) => void;
28
28
  export type PositionErrorCallback = (error: DOMException) => void;
29
29
 
30
- class Location extends FrontendPlugin {
30
+ class Location extends Plugin {
31
31
  constructor() {
32
32
  super("location");
33
33
  }