@tokamakdev/plugin-location 0.1.0-beta.120 → 0.1.0-beta.124

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
@@ -48,7 +48,9 @@ onRegionTransition(({ id, transition }) => console.log(id, transition));
48
48
  options. It resolves with a recent position no older than `maximumAge`
49
49
  milliseconds, 0 by default, or takes a new one, and rejects with
50
50
  `TimeoutError` when none arrives within `timeout` milliseconds, unlimited by
51
- default. The timeout starts once the user allows location.
51
+ default. The timeout starts once the user allows location. Android takes
52
+ positions from its fused provider at high accuracy, which combines GPS with
53
+ Wi-Fi and cell signals.
52
54
 
53
55
  `watchPosition()` returns a byte stream of JSON positions. `decodeValues<Position>`
54
56
  decodes them. Cancelling the stream ends the native watch; errors terminate it.
@@ -135,6 +137,13 @@ event:
135
137
  | `onVisit` | Apple only: `{ latitude, longitude, accuracy, arrival, departure }`, times in milliseconds or null when unknown. |
136
138
  | `onLocationError` | `{ name, message, id? }` when a background service fails, with the region's `id` when it concerns one. |
137
139
 
140
+ In Doze, the idle state of an unplugged Android device left still with its
141
+ screen off, Android starts the app for updates and region transitions alike,
142
+ but only an update's delivery lets the app use the network for a while. The
143
+ listeners for a region transition, `onStart` among them when it launches the
144
+ app, have no network until Doze ends: record the transition and send it from
145
+ a later event.
146
+
138
147
  `positions` holds the positions that arrived while the Worker handled the
139
148
  previous update, oldest first, so a slow Worker never builds a queue. Each is
140
149
  newer than those delivered before it, so a position that both updates and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.120",
3
+ "version": "0.1.0-beta.124",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -14,7 +14,7 @@
14
14
  "./android": "./android/bindings.ts"
15
15
  },
16
16
  "peerDependencies": {
17
- "@tokamakdev/plugin": "0.1.0-beta.120"
17
+ "@tokamakdev/plugin": "0.1.0-beta.124"
18
18
  },
19
19
  "files": [
20
20
  "android",
package/src/android.ts CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  Location,
9
9
  LocationListener,
10
10
  LocationManager,
11
+ LocationRequest,
12
+ LocationRequest$Builder,
11
13
  SystemClock,
12
14
  type Executor,
13
15
  } from "../android/bindings.js";
@@ -15,15 +17,15 @@ import { permissionDenied, unavailable, within } from "./errors.js";
15
17
  import type { Position, PositionOptions } from "./types.js";
16
18
 
17
19
  /**
18
- * The device's position: the provider's last known when no older than `maximumAge`, or a new fix
19
- * within `timeout`. Null when Android finds none.
20
+ * The device's position: the provider's last known when no older than `maximumAge`, or a new
21
+ * high-accuracy fix within `timeout`. Null when Android finds none.
20
22
  */
21
23
  export async function currentPosition(ctx: TokamakContext, { maximumAge = 0, timeout }: PositionOptions): Promise<Position | null> {
22
24
  const { context, manager } = await locationManager(ctx);
23
25
  const name = await provider(manager);
24
26
  const recent = await manager.getLastKnownLocation(name);
25
27
  if (recent && await age(ctx, recent) <= maximumAge) return await position(recent);
26
- const [signal, executor] = await Promise.all([CancellationSignal.new(ctx), mainExecutor(context)]);
28
+ const [request, signal, executor] = await Promise.all([highAccuracy(ctx, 0), CancellationSignal.new(ctx), mainExecutor(context)]);
27
29
  let consumer: Consumer | undefined;
28
30
  let finished = false;
29
31
  const found = new Promise<Position | null>((resolve, reject) => {
@@ -35,7 +37,7 @@ export async function currentPosition(ctx: TokamakContext, { maximumAge = 0, tim
35
37
  release(created);
36
38
  return;
37
39
  }
38
- await manager.getCurrentLocation(name, signal, executor, created);
40
+ await manager.getCurrentLocation(name, request, signal, executor, created);
39
41
  }).catch((failure: unknown) => { reject(locationError(failure as Error)); });
40
42
  });
41
43
  return within(found, timeout, () => {
@@ -52,7 +54,7 @@ export async function watchPositions(
52
54
  error: (error: unknown) => void,
53
55
  ): Promise<() => void> {
54
56
  const { context, manager } = await locationManager(ctx);
55
- const [name, executor] = await Promise.all([provider(manager), mainExecutor(context)]);
57
+ const [name, executor, request] = await Promise.all([provider(manager), mainExecutor(context), highAccuracy(ctx, 1000)]);
56
58
  const listener = await LocationListener.implement(ctx, {
57
59
  async onLocationChanged(location) {
58
60
  next(await position(location));
@@ -61,7 +63,7 @@ export async function watchPositions(
61
63
  onProviderDisabled() { error(unavailable("Location provider is unavailable")); },
62
64
  });
63
65
  try {
64
- await manager.requestLocationUpdates(name, 1000, 0, executor, listener);
66
+ await manager.requestLocationUpdates(name, request, executor, listener);
65
67
  } catch (failure) {
66
68
  release(listener);
67
69
  throw locationError(failure as Error);
@@ -84,9 +86,12 @@ async function mainExecutor(context: Context): Promise<Executor> {
84
86
  return executor;
85
87
  }
86
88
 
87
- /** The first enabled of the GPS, network and fused providers, or any other enabled provider. */
89
+ /**
90
+ * The first enabled of the fused provider, which combines GPS with Wi-Fi and cell signals, GPS and
91
+ * the network, or any other enabled provider.
92
+ */
88
93
  async function provider(manager: LocationManager): Promise<string> {
89
- for (const name of [LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER, LocationManager.FUSED_PROVIDER]) {
94
+ for (const name of [LocationManager.FUSED_PROVIDER, LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER]) {
90
95
  if (await manager.isProviderEnabled(name)) return name;
91
96
  }
92
97
  const [other] = await manager.getProviders(true);
@@ -94,6 +99,13 @@ async function provider(manager: LocationManager): Promise<string> {
94
99
  return other;
95
100
  }
96
101
 
102
+ /** A request for the most accurate fixes the provider can give, every `interval` milliseconds. */
103
+ async function highAccuracy(ctx: TokamakContext, interval: number): Promise<LocationRequest> {
104
+ const builder = await LocationRequest$Builder.new(ctx, interval);
105
+ await builder.setQuality(LocationRequest.QUALITY_HIGH_ACCURACY);
106
+ return builder.build();
107
+ }
108
+
97
109
  /** The milliseconds since Android took `location`. */
98
110
  async function age(ctx: TokamakContext, location: Location): Promise<number> {
99
111
  const [now, taken] = await Promise.all([SystemClock.elapsedRealtimeNanos(ctx), location.getElapsedRealtimeNanos()]);