@tokamakdev/plugin-location 0.1.0-beta.142 → 0.1.0-beta.144
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/android/LocationPlugin.kt +2 -8
- package/android/LocationService.kt +2 -7
- package/android/LocationTypes.kt +7 -2
- package/android/LocationUpdates.kt +1 -8
- package/package.json +2 -2
- package/src/index.ts +9 -5
- package/web/index.ts +0 -4
- package/src/errors.ts +0 -17
|
@@ -14,11 +14,9 @@ import com.tokamak.runtime.TokamakHost
|
|
|
14
14
|
import com.tokamak.runtime.TokamakPlugin
|
|
15
15
|
import com.tokamak.runtime.TokamakPluginError
|
|
16
16
|
import kotlin.coroutines.cancellation.CancellationException
|
|
17
|
-
import kotlin.coroutines.resume
|
|
18
17
|
import kotlinx.coroutines.flow.Flow
|
|
19
18
|
import kotlinx.coroutines.flow.catch
|
|
20
19
|
import kotlinx.coroutines.flow.map
|
|
21
|
-
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
22
20
|
|
|
23
21
|
class TokamakLocationPlugin(
|
|
24
22
|
private val host: TokamakHost,
|
|
@@ -105,9 +103,9 @@ class TokamakLocationPlugin(
|
|
|
105
103
|
throw TokamakPluginError.notSupported("backgroundAlways needs the location plugin's background-always build setting")
|
|
106
104
|
}
|
|
107
105
|
val asked = if (request?.precision == Precision.Approximate) setOf(COARSE) else foreground
|
|
108
|
-
val allowed =
|
|
106
|
+
val allowed = host.requestPermissions(asked).containsValue(true)
|
|
109
107
|
if (allowed) restore(updatesRemoved = false)
|
|
110
|
-
if (background && allowed)
|
|
108
|
+
if (background && allowed) host.requestPermissions(setOf(BACKGROUND))
|
|
111
109
|
return permission()
|
|
112
110
|
}
|
|
113
111
|
|
|
@@ -157,10 +155,6 @@ class TokamakLocationPlugin(
|
|
|
157
155
|
|
|
158
156
|
fun onLocationError(): Flow<LocationErrorEvent> = events.errors.stream()
|
|
159
157
|
|
|
160
|
-
/** Asks the user for [permissions], returning whether each is granted. */
|
|
161
|
-
private suspend fun ask(permissions: Set<String>): Map<String, Boolean> =
|
|
162
|
-
suspendCancellableCoroutine { continuation -> host.requestPermissions(permissions) { continuation.resume(it) } }
|
|
163
|
-
|
|
164
158
|
private fun granted(permission: String): Boolean =
|
|
165
159
|
context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
|
|
166
160
|
|
|
@@ -9,6 +9,7 @@ import android.content.Context
|
|
|
9
9
|
import android.content.Intent
|
|
10
10
|
import android.content.pm.ServiceInfo
|
|
11
11
|
import android.os.IBinder
|
|
12
|
+
import com.tokamak.runtime.tokamakNotificationIcon
|
|
12
13
|
import com.tokamak.runtime.tokamakPlugin
|
|
13
14
|
|
|
14
15
|
private const val CHANNEL = "tokamak.location"
|
|
@@ -44,7 +45,7 @@ class TokamakLocationService : Service() {
|
|
|
44
45
|
PendingIntent.getActivity(this, 0, it, PendingIntent.FLAG_IMMUTABLE)
|
|
45
46
|
}
|
|
46
47
|
return Notification.Builder(this, CHANNEL)
|
|
47
|
-
.setSmallIcon(
|
|
48
|
+
.setSmallIcon(tokamakNotificationIcon())
|
|
48
49
|
.setContentTitle(content.title)
|
|
49
50
|
.setContentText(content.body)
|
|
50
51
|
.setContentIntent(open)
|
|
@@ -52,12 +53,6 @@ class TokamakLocationService : Service() {
|
|
|
52
53
|
.build()
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
/** The app's monochrome `drawable/tokamak_notification`, or its launcher icon. */
|
|
56
|
-
private fun icon(): Int =
|
|
57
|
-
resources.getIdentifier("tokamak_notification", "drawable", packageName).takeIf { it != 0 }
|
|
58
|
-
?: applicationInfo.icon.takeIf { it != 0 }
|
|
59
|
-
?: android.R.drawable.sym_def_app_icon
|
|
60
|
-
|
|
61
56
|
companion object {
|
|
62
57
|
/** The start intent's notification: its title, body and channel. */
|
|
63
58
|
private const val NOTIFICATION_EXTRA = "com.tokamak.plugins.location.NOTIFICATION"
|
package/android/LocationTypes.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.tokamak.plugins.location
|
|
2
2
|
|
|
3
3
|
import android.location.Location
|
|
4
|
+
import android.location.LocationRequest
|
|
4
5
|
|
|
5
6
|
data class Coordinates(
|
|
6
7
|
val latitude: Double,
|
|
@@ -45,8 +46,12 @@ data class PermissionRequest(val backgroundAlways: Boolean?, val precision: Prec
|
|
|
45
46
|
|
|
46
47
|
data class SetupConfig(val significantChanges: Boolean?)
|
|
47
48
|
|
|
48
|
-
/** The request's quality, precision traded against battery use. */
|
|
49
|
-
enum class Accuracy
|
|
49
|
+
/** The request's quality, precision traded against battery use, as `LocationRequest.QUALITY_*` [quality] gives it. */
|
|
50
|
+
enum class Accuracy(val quality: Int) {
|
|
51
|
+
High(LocationRequest.QUALITY_HIGH_ACCURACY),
|
|
52
|
+
Balanced(LocationRequest.QUALITY_BALANCED_POWER_ACCURACY),
|
|
53
|
+
Low(LocationRequest.QUALITY_LOW_POWER),
|
|
54
|
+
}
|
|
50
55
|
|
|
51
56
|
/** The foreground service's notification, set from JavaScript so apps can translate it. */
|
|
52
57
|
data class ServiceNotification(val title: String, val body: String, val channel: String)
|
|
@@ -14,13 +14,6 @@ import org.json.JSONObject
|
|
|
14
14
|
private const val PREFERENCES = "tokamak.location"
|
|
15
15
|
private const val DEFAULT_INTERVAL_MILLIS = 10_000L
|
|
16
16
|
|
|
17
|
-
private val QUALITIES =
|
|
18
|
-
mapOf(
|
|
19
|
-
Accuracy.High to LocationRequest.QUALITY_HIGH_ACCURACY,
|
|
20
|
-
Accuracy.Balanced to LocationRequest.QUALITY_BALANCED_POWER_ACCURACY,
|
|
21
|
-
Accuracy.Low to LocationRequest.QUALITY_LOW_POWER,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
17
|
/**
|
|
25
18
|
* Location updates of one kind, [name]: `startUpdates`' or significant changes'. `LocationManager`
|
|
26
19
|
* sends them to [TokamakLocationReceiver] through a `PendingIntent`, starting the app's process when
|
|
@@ -82,7 +75,7 @@ internal class LocationUpdates(private val host: TokamakHost, private val name:
|
|
|
82
75
|
val accuracy = options.accuracy ?: Accuracy.High
|
|
83
76
|
val request =
|
|
84
77
|
LocationRequest.Builder(options.interval ?: DEFAULT_INTERVAL_MILLIS)
|
|
85
|
-
.setQuality(
|
|
78
|
+
.setQuality(accuracy.quality)
|
|
86
79
|
.setMinUpdateDistanceMeters((options.distance ?: 0.0).toFloat())
|
|
87
80
|
.build()
|
|
88
81
|
val updates = checkNotNull(pendingIntent(PendingIntent.FLAG_UPDATE_CURRENT))
|
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.144",
|
|
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.144"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"android",
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { capabilitiesOf, lifecycleStage, platformApi, type JsonStream, type Toka
|
|
|
2
2
|
|
|
3
3
|
import { android } from "./android.generated.js";
|
|
4
4
|
import { apple } from "./apple.generated.js";
|
|
5
|
-
import { backgroundDenied, permissionDenied, timedOut, unavailable } from "./errors.js";
|
|
6
5
|
import type {
|
|
7
6
|
LocationErrorEvent,
|
|
8
7
|
LocationPermission,
|
|
@@ -25,10 +24,10 @@ export function location(ctx: TokamakContext) {
|
|
|
25
24
|
|
|
26
25
|
/** Requires location services and in-use permission, asking for it where the user has not answered. */
|
|
27
26
|
async function authorize(): Promise<void> {
|
|
28
|
-
if (!await native.servicesEnabled()) throw
|
|
27
|
+
if (!await native.servicesEnabled()) throw new DOMException("Location services are disabled", "NotReadableError");
|
|
29
28
|
let { inUse } = await native.permission();
|
|
30
29
|
if (inUse === "prompt") ({ inUse } = await native.requestPermission());
|
|
31
|
-
if (inUse !== "granted") throw
|
|
30
|
+
if (inUse !== "granted") throw new DOMException("Location permission was denied", "NotAllowedError");
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
return {
|
|
@@ -56,13 +55,18 @@ export function location(ctx: TokamakContext) {
|
|
|
56
55
|
const recent = await native.recentPosition(Math.min(maximumAge, Number.MAX_VALUE));
|
|
57
56
|
if (recent) return recent;
|
|
58
57
|
const located = new AbortController();
|
|
59
|
-
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
58
|
+
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
59
|
+
located.abort(new DOMException("Location was not found in time", "TimeoutError"));
|
|
60
|
+
}, timeout);
|
|
60
61
|
const position = await platformApi(ctx, { apple, android }, located.signal).currentPosition()
|
|
61
62
|
.finally(() => { clearTimeout(timer); });
|
|
62
63
|
if (position) return position;
|
|
63
64
|
// Android finds no position in the background without permission at all times.
|
|
64
65
|
const background = await lifecycleStage(ctx) === "background";
|
|
65
|
-
|
|
66
|
+
if (background && (await native.permission()).backgroundAlways !== "granted") {
|
|
67
|
+
throw new DOMException("Location permission does not cover the background", "NotAllowedError");
|
|
68
|
+
}
|
|
69
|
+
throw new DOMException("Location is unavailable", "NotReadableError");
|
|
66
70
|
},
|
|
67
71
|
|
|
68
72
|
/**
|
package/web/index.ts
CHANGED
|
@@ -47,10 +47,6 @@ export async function permission(): Promise<LocationPermission> {
|
|
|
47
47
|
return { inUse: state, backgroundAlways: "denied", precision: "precise" };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/** Rejects as the web does for what only native apps have. */
|
|
51
|
-
export function unsupported(): Promise<never> {
|
|
52
|
-
return Promise.reject(new DOMException("Native location is unavailable", "NotSupportedError"));
|
|
53
|
-
}
|
|
54
50
|
|
|
55
51
|
function browserLocationApi(): Geolocation {
|
|
56
52
|
const root: { navigator?: { geolocation?: Geolocation } } = globalThis;
|
package/src/errors.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/** The errors location operations reject with. */
|
|
2
|
-
|
|
3
|
-
export function permissionDenied(): DOMException {
|
|
4
|
-
return new DOMException("Location permission was denied", "NotAllowedError");
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function backgroundDenied(): DOMException {
|
|
8
|
-
return new DOMException("Location permission does not cover the background", "NotAllowedError");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function timedOut(): DOMException {
|
|
12
|
-
return new DOMException("Location was not found in time", "TimeoutError");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function unavailable(message: string): DOMException {
|
|
16
|
-
return new DOMException(message, "NotReadableError");
|
|
17
|
-
}
|