@tokamakdev/plugin-location 0.1.0-beta.137 → 0.1.0-beta.139

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
@@ -23,7 +23,7 @@ for await (const next of decodeValues<Position>(location.watchPosition())) {
23
23
  break; // Cancels the watch.
24
24
  }
25
25
 
26
- await location.requestPermission({ background: true });
26
+ await location.requestPermission({ backgroundAlways: true });
27
27
  await location.addRegion({ id: "home", latitude: 51.5, longitude: -0.12, radius: 150 });
28
28
  ```
29
29
 
@@ -56,7 +56,7 @@ Wi-Fi and cell signals.
56
56
  decodes them. Cancelling the stream ends the native watch; errors terminate it.
57
57
 
58
58
  Both ask for in-use permission when the user has not answered. Off screen
59
- without background permission, the platforms stop locating the app, so
59
+ without permission at all times, the platforms stop locating the app, so
60
60
  `getCurrentPosition` rejects with `NotAllowedError` there. iOS still locates
61
61
  the app for a few seconds after it leaves the screen. With approximate
62
62
  permission, Android gives an app a new position at most every 10 minutes, so
@@ -66,23 +66,24 @@ permission, Android gives an app a new position at most every 10 minutes, so
66
66
 
67
67
  `permission()` reports the app's permission without asking:
68
68
 
69
- - `inUse` and `background`, each `"granted"`, `"denied"` or `"prompt"`.
70
- `background` is `"denied"` unless the `background` build setting declares
71
- it. macOS has no separate background permission, so `background` equals
72
- `inUse` there.
69
+ - `inUse` and `backgroundAlways`, each `"granted"`, `"denied"` or `"prompt"`.
70
+ `backgroundAlways` is location at all times: Apple's Always, Android's
71
+ "Allow all the time". It is `"denied"` unless the `background-always` build
72
+ setting declares it. macOS has no separate permission at all times, so
73
+ `backgroundAlways` equals `inUse` there.
73
74
  - `precision`: `"precise"` or `"approximate"`, what the app gets or would get
74
75
  once allowed.
75
76
 
76
77
  `requestPermission(options)` asks for in-use permission where the user has not
77
78
  answered, and for what `options` names, then resolves with the permission:
78
79
 
79
- - `background: true` asks for background permission.
80
+ - `backgroundAlways: true` asks for permission at all times.
80
81
  - **iOS** shows the Always prompt once, from When In Use. Asked before the
81
82
  user has answered, it gives provisional Always, which iOS confirms with
82
- the user later. Without the `background` build setting it rejects with
83
+ the user later. Without the `background-always` build setting it rejects with
83
84
  `NotSupportedError`.
84
85
  - **Android** asks for in-use permission first, as Android grants
85
- background permission only after it. From Android 11 the request opens
86
+ permission at all times only after it. From Android 11 the request opens
86
87
  the app's page in Settings, where the user picks "Allow all the time".
87
88
  - `precision: "precise"` asks for precise location where the user allowed
88
89
  approximate. Apple platforms grant it temporarily, while the app is in use,
@@ -103,7 +104,7 @@ Vite plugin's options, as `--location-<key>`, or as `TOKAMAK_LOCATION_<KEY>`:
103
104
  // vite.config.js
104
105
  tokamak({
105
106
  location: {
106
- background: true,
107
+ "background-always": true,
107
108
  "background-updates": true,
108
109
  "accuracy-purposes": { Directions: "Shows directions from where you are" },
109
110
  },
@@ -112,11 +113,11 @@ tokamak({
112
113
 
113
114
  | Setting | Kind | Declares |
114
115
  |---|---|---|
115
- | `background` | boolean | Background permission: iOS's Always reason, Android's `ACCESS_BACKGROUND_LOCATION`. |
116
+ | `background-always` | boolean | Location at all times: iOS's Always reason and the code that asks for Always, Android's `ACCESS_BACKGROUND_LOCATION`. |
116
117
  | `background-updates` | boolean | Updates off screen: iOS's `location` background mode, Android's location foreground service and its permissions. |
117
118
  | `precision` | `precise` or `approximate` | `approximate` declares approximate location only: iOS's `NSLocationDefaultAccuracyReduced`, and Android without `ACCESS_FINE_LOCATION`. macOS has no such declaration. |
118
119
  | `when-in-use-description` | string | Apple: the in-use prompt's reason, and macOS's only reason. |
119
- | `always-description` | string | iOS: the Always prompt's reason. Needs `background`. |
120
+ | `always-description` | string | iOS: the Always prompt's reason. Needs `background-always`. |
120
121
  | `accuracy-purposes` | map of string | Apple: the reasons for temporary precise location, by purpose key. |
121
122
 
122
123
  Without a description the plugin uses a generic English reason. Declare only
@@ -174,7 +175,7 @@ monitor significant changes.
174
175
  - `visits`, Apple only, delivers arrivals at and departures from the places
175
176
  the user stays to `onVisit`. It uses the least power of all.
176
177
 
177
- With background permission, iOS relaunches the app for both, and Android starts
178
+ With permission at all times, iOS relaunches the app for both, and Android starts
178
179
  the app's process for significant changes. [Regions](#regions) wake the app
179
180
  the same way.
180
181
 
@@ -212,18 +213,18 @@ app's process for each update.
212
213
  - `accuracy` is `"high"` (default), `"balanced"` or `"low"`; `distance` the
213
214
  metres between updates, 0 by default; `interval` the milliseconds between
214
215
  them, 10 seconds by default.
215
- - Off screen, updates need background permission and arrive a few times an
216
+ - Off screen, updates need permission at all times and arrive a few times an
216
217
  hour, unless they run in a foreground service.
217
218
  - `foregroundService: { title, body, channel }` runs updates in a foreground
218
219
  service, with that notification in a channel of that name, at the requested
219
220
  rate off screen. It needs the `background-updates` build setting, and
220
- rejects with `NotSupportedError` without it. Off screen it needs background
221
- permission, and Android starts the service only within 10 seconds of an
221
+ rejects with `NotSupportedError` without it. Off screen it needs permission
222
+ at all times, and Android starts the service only within 10 seconds of an
222
223
  update; otherwise it rejects with `NeedsUIError`. A region transition does
223
224
  not allow the start.
224
225
  - The plugin requests updates again after a restart, an app update and a
225
226
  force stop, and starts the service again on screen, or after a restart or
226
- update with background permission. Android restarts a service it ends for
227
+ update with permission at all times. Android restarts a service it ends for
227
228
  memory.
228
229
 
229
230
  ## Regions
@@ -237,7 +238,7 @@ entry. `getRegions()` resolves with the regions, each with its `state` on
237
238
  Apple platforms: `"inside"`, `"outside"` or `"unknown"`.
238
239
 
239
240
  Regions need precise location, and reject with `NotAllowedError` without it,
240
- and background permission off screen.
241
+ and permission at all times off screen.
241
242
 
242
243
  - **Apple** uses `CLMonitor`, which monitors up to 20 regions; `addRegion`
243
244
  rejects with `QuotaExceededError` beyond them. iOS relaunches the app for a
@@ -137,14 +137,14 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
137
137
  private fun TokamakValue.asLocationPermission(): LocationPermission =
138
138
  LocationPermission(
139
139
  inUse = this["inUse"].enum(Permission.entries),
140
- background = this["background"].enum(Permission.entries),
140
+ backgroundAlways = this["backgroundAlways"].enum(Permission.entries),
141
141
  precision = this["precision"].enum(Precision.entries),
142
142
  )
143
143
 
144
144
  private fun json(value: LocationPermission): JSONObject =
145
145
  JSONObject()
146
146
  .put("inUse", jsonName(value.inUse.name))
147
- .put("background", jsonName(value.background.name))
147
+ .put("backgroundAlways", jsonName(value.backgroundAlways.name))
148
148
  .put("precision", jsonName(value.precision.name))
149
149
 
150
150
  private fun TokamakValue.asLocationUpdate(): LocationUpdate =
@@ -177,13 +177,13 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
177
177
 
178
178
  private fun TokamakValue.asPermissionRequest(): PermissionRequest =
179
179
  PermissionRequest(
180
- background = this["background"].nullable { it.boolean() },
180
+ backgroundAlways = this["backgroundAlways"].nullable { it.boolean() },
181
181
  precision = this["precision"].nullable { it.enum(Precision.entries) },
182
182
  )
183
183
 
184
184
  private fun json(value: PermissionRequest): JSONObject =
185
185
  JSONObject()
186
- .putOpt("background", value.background)
186
+ .putOpt("backgroundAlways", value.backgroundAlways)
187
187
  .putOpt("precision", value.precision?.let { jsonName(it.name) })
188
188
 
189
189
  private fun TokamakValue.asPosition(): Position =
@@ -10,7 +10,7 @@ object LocationExports : TokamakExports<TokamakLocationPlugin> {
10
10
  e.call(TokamakLocationPlugin::recentPosition, doc = "The provider's last known position, when it is no older than `maximumAge`.")
11
11
  e.call(TokamakLocationPlugin::currentPosition, doc = "A new fix, or null when Android finds none. Cancelling the call stops locating.")
12
12
  e.stream(TokamakLocationPlugin::watchPosition, overflow = TokamakOverflow.DROP_OLDEST, doc = "Positions as the device moves.")
13
- e.call(TokamakLocationPlugin::permission, doc = "The app's in-use and background permission, and the precision it gets.")
13
+ e.call(TokamakLocationPlugin::permission, doc = "The app's location permission, and the precision it gets.")
14
14
  e.call(TokamakLocationPlugin::requestPermission, doc = "Asks for the permission `request` names where the user has not answered.")
15
15
  e.call(TokamakLocationPlugin::servicesEnabled, doc = "Whether location services are on for the device.")
16
16
  e.call(TokamakLocationPlugin::openSettings, doc = "Opens the app's page in the system settings.")
@@ -87,7 +87,7 @@ class TokamakLocationPlugin(
87
87
  /** Each position as the device moves, until the provider turns off. */
88
88
  fun watchPosition(): Flow<Position> = positions.watch().map(::Position).catch { throw locationError(it) }
89
89
 
90
- /** The app's in-use and background permission, and the precision it gets, or would get once allowed. */
90
+ /** The app's location permission, and the precision it gets, or would get once allowed. */
91
91
  fun permission(): LocationPermission {
92
92
  val inUse = if (hasForegroundPermission()) Permission.Granted else state(COARSE)
93
93
  val background = if (BACKGROUND in declared) state(BACKGROUND) else Permission.Denied
@@ -100,9 +100,9 @@ class TokamakLocationPlugin(
100
100
  * Android grants background permission only to an app with foreground permission.
101
101
  */
102
102
  suspend fun requestPermission(request: PermissionRequest?): LocationPermission {
103
- val background = request?.background == true
103
+ val background = request?.backgroundAlways == true
104
104
  if (background && BACKGROUND !in declared) {
105
- throw TokamakPluginError.notSupported("Background location needs the location plugin's background build setting")
105
+ throw TokamakPluginError.notSupported("backgroundAlways needs the location plugin's background-always build setting")
106
106
  }
107
107
  val asked = if (request?.precision == Precision.Approximate) setOf(COARSE) else foreground
108
108
  val allowed = ask(asked).containsValue(true)
@@ -39,9 +39,9 @@ enum class Permission { Granted, Denied, Prompt }
39
39
 
40
40
  enum class Precision { Precise, Approximate }
41
41
 
42
- data class LocationPermission(val inUse: Permission, val background: Permission, val precision: Precision)
42
+ data class LocationPermission(val inUse: Permission, val backgroundAlways: Permission, val precision: Precision)
43
43
 
44
- data class PermissionRequest(val background: Boolean?, val precision: Precision?)
44
+ data class PermissionRequest(val backgroundAlways: Boolean?, val precision: Precision?)
45
45
 
46
46
  data class SetupConfig(val significantChanges: Boolean?)
47
47
 
@@ -8,7 +8,7 @@ extension TokamakLocationPlugin: TokamakExports {
8
8
  e.call(currentPosition, doc: "A new fix. Cancelling the call stops locating.")
9
9
  e.stream(watchPosition, overflow: .dropOldest, doc: "Positions as the device moves.")
10
10
  e.call(
11
- permission, doc: "The app's in-use and background permission, and the precision it gets.")
11
+ permission, doc: "The app's location permission, and the precision it gets.")
12
12
  e.call(
13
13
  requestPermission,
14
14
  doc: "Asks for the permission `request` names where the user has not answered.")
@@ -39,7 +39,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
39
39
  private let events: TokamakLocationEvents
40
40
  private let updates: TokamakLocationUpdates
41
41
  private let regions: TokamakLocationRegions
42
- #if os(iOS)
42
+ #if os(iOS) && LOCATION_ALWAYS
43
43
  /// The `CLServiceSession` iOS 18 needs for updates and regions to reach an
44
44
  /// app that is not in use, held while the app has Always permission.
45
45
  private var alwaysSession: AnyObject?
@@ -107,7 +107,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
107
107
  let status = authorization.authorizationStatus
108
108
  return LocationPermission(
109
109
  inUse: Self.state(status),
110
- background: backgroundPermission(status),
110
+ backgroundAlways: alwaysPermission(status),
111
111
  precision: authorization.accuracyAuthorization == .reducedAccuracy ? .approximate : .precise
112
112
  )
113
113
  }
@@ -117,10 +117,10 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
117
117
  /// location when `request` names a purpose.
118
118
  @MainActor
119
119
  func requestPermission(_ request: PermissionRequest?) async throws -> LocationPermission {
120
- let always = request?.background == true
120
+ let always = request?.backgroundAlways == true
121
121
  if always && !Self.declaresAlways {
122
122
  throw TokamakPluginError.notSupported(
123
- "Background location needs the location plugin's background build setting")
123
+ "backgroundAlways needs the location plugin's background-always build setting")
124
124
  }
125
125
  let purpose = try Self.purpose(request)
126
126
  let status = authorization.authorizationStatus
@@ -131,14 +131,21 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
131
131
  return try await withCheckedThrowingContinuation { answer in
132
132
  prompts[UUID()] = Prompt(purpose: purpose, answer: answer)
133
133
  if always {
134
- defaults.set(true, forKey: askedForAlwaysKey)
135
- authorization.requestAlwaysAuthorization()
134
+ requestAlways()
136
135
  } else {
137
136
  authorization.requestWhenInUseAuthorization()
138
137
  }
139
138
  }
140
139
  }
141
140
 
141
+ /// Asks for Always permission, in a build that declares it.
142
+ private func requestAlways() {
143
+ #if LOCATION_ALWAYS
144
+ defaults.set(true, forKey: askedForAlwaysKey)
145
+ authorization.requestAlwaysAuthorization()
146
+ #endif
147
+ }
148
+
142
149
  /// Whether location services are on, which Core Location reports off the
143
150
  /// main thread.
144
151
  func servicesEnabled() async -> Bool {
@@ -268,7 +275,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
268
275
  }
269
276
  }
270
277
 
271
- private func backgroundPermission(_ status: CLAuthorizationStatus) -> Permission {
278
+ private func alwaysPermission(_ status: CLAuthorizationStatus) -> Permission {
272
279
  if status == .authorizedAlways { return .granted }
273
280
  let asks = status == .notDetermined ? Self.declaresAlways : canUpgradeToAlways(status)
274
281
  return asks ? .prompt : .denied
@@ -296,7 +303,7 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
296
303
  }
297
304
 
298
305
  private func holdAlwaysSession() {
299
- #if os(iOS)
306
+ #if os(iOS) && LOCATION_ALWAYS
300
307
  guard #available(iOS 18, *) else { return }
301
308
  let always = authorization.authorizationStatus == .authorizedAlways
302
309
  if always && alwaysSession == nil {
@@ -316,10 +323,13 @@ final class TokamakLocationPlugin: NSObject, TokamakPlugin,
316
323
  }
317
324
  }
318
325
 
319
- /// Whether the Info.plist declares Always permission, as the `background`
320
- /// build setting does on iOS and every macOS build does.
321
- private static let declaresAlways =
322
- Bundle.main.object(forInfoDictionaryKey: "NSLocationAlwaysAndWhenInUseUsageDescription") != nil
326
+ #if LOCATION_ALWAYS
327
+ /// Whether the build declares Always permission, as the `background-always`
328
+ /// build setting does on iOS and every macOS build does.
329
+ private static let declaresAlways = true
330
+ #else
331
+ private static let declaresAlways = false
332
+ #endif
323
333
 
324
334
  private var askedForAlways: Bool {
325
335
  defaults.bool(forKey: askedForAlwaysKey)
@@ -95,12 +95,12 @@ extension TokamakLocationPlugin {
95
95
 
96
96
  struct LocationPermission: Codable {
97
97
  let inUse: Permission
98
- let background: Permission
98
+ let backgroundAlways: Permission
99
99
  let precision: Precision
100
100
  }
101
101
 
102
102
  struct PermissionRequest: Codable {
103
- let background: Bool?
103
+ let backgroundAlways: Bool?
104
104
  let precision: Precision?
105
105
  /// The key in the app's `NSLocationTemporaryUsageDescriptionDictionary` of the reason to show.
106
106
  let purpose: String?
@@ -1,6 +1,6 @@
1
1
  /** The location plugin's build settings, with their defaults applied. */
2
2
  export interface Settings {
3
- background: boolean;
3
+ "background-always": boolean;
4
4
  "background-updates": boolean;
5
5
  precision: "precise" | "approximate";
6
6
  "when-in-use-description"?: string;
@@ -8,12 +8,16 @@ export interface Settings {
8
8
  "accuracy-purposes"?: Record<string, string>;
9
9
  }
10
10
 
11
- /** The file that declares what the settings ask for, the plugin section key naming it, and notices. */
11
+ /**
12
+ * The file that declares what the settings ask for, the plugin section key naming it, the Swift
13
+ * compilation conditions of an Apple build, and notices.
14
+ */
12
15
  export interface Declarations {
13
16
  key: "plist" | "manifest";
14
17
  name: string;
15
18
  contents: string;
16
19
  notices: string[];
20
+ conditions?: string[];
17
21
  }
18
22
 
19
23
  export function declarations(platform: string, settings: Settings): Declarations | { problems: string[] };
@@ -6,11 +6,12 @@ const ALWAYS = "This app uses your location in the background for location featu
6
6
 
7
7
  /**
8
8
  * The file that declares what `settings` ask for on `platform`, the plugin section key that names
9
- * it, and notices for the build; or the problems with the settings.
9
+ * it, the Swift compilation conditions of an Apple build, and notices for the build; or the
10
+ * problems with the settings.
10
11
  */
11
12
  export function declarations(platform, settings) {
12
- if (settings["always-description"] !== undefined && !settings.background) {
13
- return { problems: ["always-description needs background, which declares the permission it describes"] };
13
+ if (settings["always-description"] !== undefined && !settings["background-always"]) {
14
+ return { problems: ["always-description needs background-always, which declares the permission it describes"] };
14
15
  }
15
16
  if (platform === "android") {
16
17
  return { key: "manifest", name: "AndroidManifest.xml", contents: androidManifest(settings), notices: [] };
@@ -19,7 +20,16 @@ export function declarations(platform, settings) {
19
20
  platform === "macos" && settings.precision === "approximate"
20
21
  ? ["macOS has no declaration of approximate location, so precision approximate applies to iOS and Android only"]
21
22
  : [];
22
- return { key: "plist", name: "Info.plist", contents: plist(appleKeys(platform, settings)), notices };
23
+ const keys = appleKeys(platform, settings);
24
+ // Only a build that gives the Always reason compiles the code that asks for Always.
25
+ const always = "NSLocationAlwaysAndWhenInUseUsageDescription" in keys;
26
+ return {
27
+ key: "plist",
28
+ name: "Info.plist",
29
+ contents: plist(keys),
30
+ notices,
31
+ ...(always ? { conditions: ["LOCATION_ALWAYS"] } : {}),
32
+ };
23
33
  }
24
34
 
25
35
  /** The Info.plist keys of `settings` on the Apple `platform`. */
@@ -37,7 +47,7 @@ function appleKeys(platform, settings) {
37
47
  };
38
48
  }
39
49
  const keys = { NSLocationWhenInUseUsageDescription: whenInUse };
40
- if (settings.background) keys.NSLocationAlwaysAndWhenInUseUsageDescription = settings["always-description"] ?? ALWAYS;
50
+ if (settings["background-always"]) keys.NSLocationAlwaysAndWhenInUseUsageDescription = settings["always-description"] ?? ALWAYS;
41
51
  if (settings["background-updates"]) keys.UIBackgroundModes = ["location"];
42
52
  if (settings.precision === "approximate") keys.NSLocationDefaultAccuracyReduced = true;
43
53
  return { ...keys, ...temporary };
@@ -47,7 +57,7 @@ function appleKeys(platform, settings) {
47
57
  function androidManifest(settings) {
48
58
  const permissions = ["ACCESS_COARSE_LOCATION", "RECEIVE_BOOT_COMPLETED"];
49
59
  if (settings.precision !== "approximate") permissions.push("ACCESS_FINE_LOCATION");
50
- if (settings.background) permissions.push("ACCESS_BACKGROUND_LOCATION");
60
+ if (settings["background-always"]) permissions.push("ACCESS_BACKGROUND_LOCATION");
51
61
  if (settings["background-updates"]) permissions.push("FOREGROUND_SERVICE", "FOREGROUND_SERVICE_LOCATION");
52
62
  const service = settings["background-updates"]
53
63
  ? [
package/build/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  // The location plugin's build script: tok runs it for each platform with the plugin's resolved
2
2
  // build settings on stdin. It writes the plugin's Info.plist or Android manifest into `output`
3
- // and prints the key that names it.
3
+ // and prints the keys that name it and an Apple build's compilation conditions.
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { declarations } from "./declarations.mjs";
7
7
 
8
8
  const { platform, settings, output } = JSON.parse(readFileSync(0, "utf8"));
9
- const { key, name, contents, notices, problems } = declarations(platform, settings);
9
+ const { key, name, contents, notices, conditions, problems } = declarations(platform, settings);
10
10
  if (problems) {
11
11
  console.error(problems.join("\n"));
12
12
  process.exit(1);
@@ -14,4 +14,4 @@ if (problems) {
14
14
  for (const notice of notices) console.error(notice);
15
15
  const path = join(output, name);
16
16
  writeFileSync(path, contents);
17
- console.log(JSON.stringify({ [key]: path }));
17
+ console.log(JSON.stringify({ [key]: path, "compilation-conditions": conditions }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.137",
3
+ "version": "0.1.0-beta.139",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -14,7 +14,7 @@
14
14
  "./events": "./src/events.ts"
15
15
  },
16
16
  "peerDependencies": {
17
- "@tokamakdev/plugin": "0.1.0-beta.137"
17
+ "@tokamakdev/plugin": "0.1.0-beta.139"
18
18
  },
19
19
  "files": [
20
20
  "android",
@@ -21,7 +21,7 @@ export interface LocationErrorEvent {
21
21
 
22
22
  export interface LocationPermission {
23
23
  readonly inUse: Permission;
24
- readonly background: Permission;
24
+ readonly backgroundAlways: Permission;
25
25
  readonly precision: Precision;
26
26
  }
27
27
 
@@ -41,7 +41,7 @@ export interface MonitoredRegion {
41
41
  export type Permission = "granted" | "denied" | "prompt";
42
42
 
43
43
  export interface PermissionRequest {
44
- readonly background?: boolean;
44
+ readonly backgroundAlways?: boolean;
45
45
  readonly precision?: Precision;
46
46
  }
47
47
 
@@ -96,7 +96,7 @@ export interface Android {
96
96
  currentPosition(): Promise<Position | null>;
97
97
  /** Positions as the device moves. */
98
98
  watchPosition(): JsonStream<Position>;
99
- /** The app's in-use and background permission, and the precision it gets. */
99
+ /** The app's location permission, and the precision it gets. */
100
100
  permission(): Promise<LocationPermission>;
101
101
  /** Asks for the permission `request` names where the user has not answered. */
102
102
  requestPermission(request?: PermissionRequest | null): Promise<LocationPermission>;
@@ -22,7 +22,7 @@ export interface LocationErrorEvent {
22
22
 
23
23
  export interface LocationPermission {
24
24
  readonly inUse: Permission;
25
- readonly background: Permission;
25
+ readonly backgroundAlways: Permission;
26
26
  readonly precision: Precision;
27
27
  }
28
28
 
@@ -44,7 +44,7 @@ export interface MonitoredRegion {
44
44
  export type Permission = "granted" | "denied" | "prompt";
45
45
 
46
46
  export interface PermissionRequest {
47
- readonly background?: boolean;
47
+ readonly backgroundAlways?: boolean;
48
48
  readonly precision?: Precision;
49
49
  readonly purpose?: string;
50
50
  }
@@ -102,7 +102,7 @@ export interface Apple {
102
102
  currentPosition(): Promise<Position>;
103
103
  /** Positions as the device moves. */
104
104
  watchPosition(): JsonStream<Position>;
105
- /** The app's in-use and background permission, and the precision it gets. */
105
+ /** The app's location permission, and the precision it gets. */
106
106
  permission(): Promise<LocationPermission>;
107
107
  /** Asks for the permission `request` names where the user has not answered. */
108
108
  requestPermission(request?: PermissionRequest | null): Promise<LocationPermission>;
package/src/index.ts CHANGED
@@ -60,9 +60,9 @@ export function location(ctx: TokamakContext) {
60
60
  const position = await platformApi(ctx, { apple, android }, located.signal).currentPosition()
61
61
  .finally(() => { clearTimeout(timer); });
62
62
  if (position) return position;
63
- // Android finds no position in the background without background permission.
63
+ // Android finds no position in the background without permission at all times.
64
64
  const background = await lifecycleStage(ctx) === "background";
65
- throw background && (await native.permission()).background !== "granted" ? backgroundDenied() : unavailable("Location is unavailable");
65
+ throw background && (await native.permission()).backgroundAlways !== "granted" ? backgroundDenied() : unavailable("Location is unavailable");
66
66
  },
67
67
 
68
68
  /**
@@ -74,12 +74,12 @@ export function location(ctx: TokamakContext) {
74
74
  return native.watchPosition();
75
75
  },
76
76
 
77
- /** The app's in-use and background permission, and the precision it gets. Never asks. */
77
+ /** The app's location permission, and the precision it gets. Never asks. */
78
78
  permission: (): Promise<LocationPermission> => native.permission(),
79
79
 
80
80
  /**
81
81
  * Asks for in-use permission, and for what `options` names, where the user has not answered.
82
- * Android asks for background permission once in-use permission is granted. Rejects with
82
+ * Android asks for permission at all times once in-use permission is granted. Rejects with
83
83
  * `NeedsUIError` when a prompt would show off screen.
84
84
  */
85
85
  requestPermission: (options?: PermissionRequest): Promise<LocationPermission> => native.requestPermission(options),
package/src/types.ts CHANGED
@@ -28,7 +28,7 @@ export type PositionErrorCallback = (error: DOMException) => void;
28
28
  /**
29
29
  * Long-term location awareness, which runs across launches and restarts, uses little power and shows
30
30
  * no indicator: `setup` starts what it names and stops what it leaves out. Off screen it needs
31
- * background permission, with which the system launches the app for its events.
31
+ * permission at all times, with which the system launches the app for its events.
32
32
  */
33
33
  export interface SetupConfig {
34
34
  /**
@@ -50,16 +50,16 @@ export type Precision = "precise" | "approximate";
50
50
  export interface LocationPermission {
51
51
  /** Location while the app is in use. */
52
52
  readonly inUse: Permission;
53
- /** Location while it is not: Apple's Always, Android's "Allow all the time". The same as `inUse` on macOS. */
54
- readonly background: Permission;
53
+ /** Location at all times: Apple's Always, Android's "Allow all the time". The same as `inUse` on macOS. */
54
+ readonly backgroundAlways: Permission;
55
55
  /** The precision the app gets, or would get once allowed. */
56
56
  readonly precision: Precision;
57
57
  }
58
58
 
59
59
  /** What `requestPermission` asks for besides in-use permission. */
60
60
  export interface PermissionRequest {
61
- /** Background permission, which needs the `background` build setting. */
62
- readonly background?: boolean;
61
+ /** Location at all times, which needs the `background-always` build setting. */
62
+ readonly backgroundAlways?: boolean;
63
63
  /**
64
64
  * `"precise"` asks for precise location where the user allowed approximate: on Apple platforms
65
65
  * temporarily, for `purpose`. `"approximate"` asks Android for approximate location only.
@@ -2,10 +2,10 @@
2
2
  "schemaVersion": 2,
3
3
  "id": "location",
4
4
  "settings": {
5
- "background": {
5
+ "background-always": {
6
6
  "kind": "boolean",
7
7
  "default": false,
8
- "description": "Declares background location permission: Apple's Always, Android's ACCESS_BACKGROUND_LOCATION"
8
+ "description": "Declares location permission at all times: Apple's Always, Android's ACCESS_BACKGROUND_LOCATION"
9
9
  },
10
10
  "background-updates": {
11
11
  "kind": "boolean",
package/web/index.ts CHANGED
@@ -44,7 +44,7 @@ export async function permission(): Promise<LocationPermission> {
44
44
  const permissions = (globalThis as { navigator?: { permissions?: Permissions } }).navigator?.permissions;
45
45
  if (!permissions) throw new DOMException("The Permissions API is unavailable", "NotSupportedError");
46
46
  const { state } = await permissions.query({ name: "geolocation" });
47
- return { inUse: state, background: "denied", precision: "precise" };
47
+ return { inUse: state, backgroundAlways: "denied", precision: "precise" };
48
48
  }
49
49
 
50
50
  /** Rejects as the web does for what only native apps have. */