@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 +19 -18
- package/android/Exports.generated.kt +4 -4
- package/android/Exports.kt +1 -1
- package/android/LocationPlugin.kt +3 -3
- package/android/LocationTypes.kt +2 -2
- package/apple/Exports.swift +1 -1
- package/apple/LocationPlugin.swift +22 -12
- package/apple/LocationTypes.swift +2 -2
- package/build/declarations.d.mts +6 -2
- package/build/declarations.mjs +16 -6
- package/build/index.mjs +3 -3
- package/package.json +2 -2
- package/src/android.generated.ts +3 -3
- package/src/apple.generated.ts +3 -3
- package/src/index.ts +4 -4
- package/src/types.ts +5 -5
- package/tokamak-plugin.json +2 -2
- package/web/index.ts +1 -1
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({
|
|
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
|
|
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 `
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
- `
|
|
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
|
-
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
221
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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("
|
|
186
|
+
.putOpt("backgroundAlways", value.backgroundAlways)
|
|
187
187
|
.putOpt("precision", value.precision?.let { jsonName(it.name) })
|
|
188
188
|
|
|
189
189
|
private fun TokamakValue.asPosition(): Position =
|
package/android/Exports.kt
CHANGED
|
@@ -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
|
|
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
|
|
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?.
|
|
103
|
+
val background = request?.backgroundAlways == true
|
|
104
104
|
if (background && BACKGROUND !in declared) {
|
|
105
|
-
throw TokamakPluginError.notSupported("
|
|
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)
|
package/android/LocationTypes.kt
CHANGED
|
@@ -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
|
|
42
|
+
data class LocationPermission(val inUse: Permission, val backgroundAlways: Permission, val precision: Precision)
|
|
43
43
|
|
|
44
|
-
data class PermissionRequest(val
|
|
44
|
+
data class PermissionRequest(val backgroundAlways: Boolean?, val precision: Precision?)
|
|
45
45
|
|
|
46
46
|
data class SetupConfig(val significantChanges: Boolean?)
|
|
47
47
|
|
package/apple/Exports.swift
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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?.
|
|
120
|
+
let always = request?.backgroundAlways == true
|
|
121
121
|
if always && !Self.declaresAlways {
|
|
122
122
|
throw TokamakPluginError.notSupported(
|
|
123
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
98
|
+
let backgroundAlways: Permission
|
|
99
99
|
let precision: Precision
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
struct PermissionRequest: Codable {
|
|
103
|
-
let
|
|
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?
|
package/build/declarations.d.mts
CHANGED
|
@@ -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
|
-
/**
|
|
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[] };
|
package/build/declarations.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
17
|
+
"@tokamakdev/plugin": "0.1.0-beta.139"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"android",
|
package/src/android.generated.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface LocationErrorEvent {
|
|
|
21
21
|
|
|
22
22
|
export interface LocationPermission {
|
|
23
23
|
readonly inUse: Permission;
|
|
24
|
-
readonly
|
|
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
|
|
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
|
|
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>;
|
package/src/apple.generated.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface LocationErrorEvent {
|
|
|
22
22
|
|
|
23
23
|
export interface LocationPermission {
|
|
24
24
|
readonly inUse: Permission;
|
|
25
|
-
readonly
|
|
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
|
|
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
|
|
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
|
|
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()).
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
54
|
-
readonly
|
|
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
|
-
/**
|
|
62
|
-
readonly
|
|
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.
|
package/tokamak-plugin.json
CHANGED
|
@@ -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
|
|
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,
|
|
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. */
|