@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.
@@ -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 = ask(asked).containsValue(true)
106
+ val allowed = host.requestPermissions(asked).containsValue(true)
109
107
  if (allowed) restore(updatesRemoved = false)
110
- if (background && allowed) ask(setOf(BACKGROUND))
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(icon())
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"
@@ -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 { High, Balanced, Low }
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(QUALITIES.getValue(accuracy))
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.142",
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.142"
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 unavailable("Location services are disabled");
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 permissionDenied();
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(() => { located.abort(timedOut()); }, timeout);
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
- throw background && (await native.permission()).backgroundAlways !== "granted" ? backgroundDenied() : unavailable("Location is unavailable");
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
- }