@tokamakdev/plugin-location 0.1.0-beta.104 → 0.1.0-beta.107

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
@@ -127,9 +127,11 @@ event:
127
127
  | `onLocationError` | `{ name, message, id? }` when a background service fails, with the region's `id` when it concerns one. |
128
128
 
129
129
  `positions` holds the positions that arrived while the Worker handled the
130
- previous update, oldest first, so a slow Worker never builds a queue. Apple's
131
- `stationary`, from iOS 18 and macOS 15, is true on the last update before
132
- updates pause while the device is still.
130
+ previous update, oldest first, so a slow Worker never builds a queue. Each is
131
+ newer than those delivered before it, so a position that both updates and
132
+ significant changes report arrives once. Apple's `stationary`, from iOS 18
133
+ and macOS 15, is true on the last update before updates pause while the
134
+ device is still.
133
135
 
134
136
  The plugin's `onLocationUpdate`, `onRegionTransition`, `onVisit` and
135
137
  `onLocationError` methods receive the same events while the listening page or
@@ -175,7 +177,8 @@ plugin keeps them across launches and restarts.
175
177
  macOS 27). It tunes when updates pause and how coordinates follow roads.
176
178
  - Updates pause while the device is still and resume when it moves.
177
179
  - If iOS ends the app while updates run, it launches the app for the next
178
- update, and the plugin resumes them.
180
+ update, and the plugin resumes them until `stopUpdates()`. With Always
181
+ permission, swiping the app away does not stop them either.
179
182
  - Off screen, updates need Always permission, or the `background-updates`
180
183
  build setting, whose background activity session shows the pill.
181
184
  `startUpdates` rejects with `NeedsUIError` off screen on iOS.
@@ -23,6 +23,9 @@ internal class LocationEvents(private val host: TokamakHost) {
23
23
  private val finishes = mutableListOf<() -> Unit>()
24
24
  private var updating = false
25
25
 
26
+ /** The time of the newest position delivered, as requests that run together receive the same positions. */
27
+ private var latest = Long.MIN_VALUE
28
+
26
29
  /** Adds a page's or Worker's listener of [method], returning its removal, or null for another method. */
27
30
  fun listen(method: String, reply: TokamakPluginReply): (() -> Unit)? {
28
31
  if (method !in LISTENERS) return null
@@ -32,11 +35,14 @@ internal class LocationEvents(private val host: TokamakHost) {
32
35
  }
33
36
 
34
37
  /**
35
- * Delivers [received] positions, then calls [finish]. Positions that arrive while the Worker
36
- * handles an update reach it together as the next.
38
+ * Delivers the [received] positions newer than those delivered, then calls [finish]. Positions
39
+ * that arrive while the Worker handles an update reach it together as the next.
37
40
  */
38
41
  fun update(received: List<JSONObject>, finish: () -> Unit) {
39
- positions += received
42
+ val newer = received.filter { it.getLong("timestamp") > latest }
43
+ if (newer.isEmpty()) return finish()
44
+ latest = newer.maxOf { it.getLong("timestamp") }
45
+ positions += newer
40
46
  finishes += finish
41
47
  if (!updating) sendUpdate()
42
48
  }
@@ -53,6 +53,9 @@ class TokamakLocationPlugin(
53
53
  private val regions = LocationRegions(context)
54
54
  private val events = LocationEvents(host)
55
55
 
56
+ /** Whether the provider was on when last reported, as each request reports its changes. */
57
+ private var providerEnabled = true
58
+
56
59
  /** Whether the app's manifest declares the foreground service, as the `background-updates` build setting does. */
57
60
  private val declaresService: Boolean by lazy {
58
61
  runCatching { context.packageManager.getServiceInfo(ComponentName(context, TokamakLocationService::class.java), 0) }
@@ -259,12 +262,13 @@ class TokamakLocationPlugin(
259
262
  internal fun receive(intent: Intent, finish: () -> Unit) {
260
263
  if (intent.action == LocationRegions.REGION_ACTION) return transition(intent, finish)
261
264
  if (intent.hasExtra(LocationManager.KEY_PROVIDER_ENABLED)) {
262
- if (intent.getBooleanExtra(LocationManager.KEY_PROVIDER_ENABLED, true)) return finish()
265
+ val enabled = intent.getBooleanExtra(LocationManager.KEY_PROVIDER_ENABLED, true)
266
+ val turnedOff = providerEnabled && !enabled
267
+ providerEnabled = enabled
268
+ if (!turnedOff) return finish()
263
269
  return events.fail(TokamakPluginError("NotReadableError", "The location provider was turned off"), finish)
264
270
  }
265
- val received = locations(intent).map(::position)
266
- if (received.isEmpty()) return finish()
267
- events.update(received, finish)
271
+ events.update(locations(intent).map(::position), finish)
268
272
  }
269
273
 
270
274
  /** Delivers a region's entry or exit, when the region reports it. */
@@ -19,6 +19,9 @@ final class TokamakLocationEvents {
19
19
  private var positions: [[String: Any]] = []
20
20
  private var stationary: Bool?
21
21
  private var updating = false
22
+ /// The time of the newest position delivered, as services that run together
23
+ /// report the same positions.
24
+ private var latest = Date.distantPast
22
25
  /// The messages of the problems last reported, by region id, or nil for
23
26
  /// updates.
24
27
  private var problems: [String?: Set<String>] = [:]
@@ -37,11 +40,14 @@ final class TokamakLocationEvents {
37
40
  }
38
41
  }
39
42
 
40
- /// Delivers `location`, with Apple's `stationary` flag when the system
41
- /// reports it. Positions that arrive while the Worker handles an update
42
- /// reach it together as the next.
43
+ /// Delivers `location` when it is newer than the positions delivered, with
44
+ /// Apple's `stationary` flag when the system reports it. Positions that
45
+ /// arrive while the Worker handles an update reach it together as the next.
43
46
  func update(_ location: CLLocation?, stationary: Bool? = nil) {
44
- positions += location.map { [Self.position($0)] } ?? []
47
+ if let location, location.timestamp > latest {
48
+ latest = location.timestamp
49
+ positions.append(Self.position(location))
50
+ }
45
51
  if let stationary {
46
52
  self.stationary = stationary
47
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.104",
3
+ "version": "0.1.0-beta.107",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -12,7 +12,7 @@
12
12
  "./events": "./events/index.ts"
13
13
  },
14
14
  "peerDependencies": {
15
- "@tokamakdev/plugin": "0.1.0-beta.104"
15
+ "@tokamakdev/plugin": "0.1.0-beta.107"
16
16
  },
17
17
  "files": [
18
18
  "android",
package/src/types.ts CHANGED
@@ -112,11 +112,14 @@ export interface ServiceNotification {
112
112
 
113
113
  /** Positions from continuous updates or significant changes. */
114
114
  export interface LocationUpdate {
115
- /** Oldest first: those that arrived while the Worker handled the previous update come together. */
115
+ /**
116
+ * Oldest first: those that arrived while the Worker handled the previous update come together. Each
117
+ * is newer than those delivered before it, so one that several services report arrives once.
118
+ */
116
119
  readonly positions: readonly Position[];
117
120
  /**
118
121
  * Apple only, from iOS 18 and macOS 15: true on the last update before updates pause while the
119
- * device is still. `positions` is empty when the update has no position.
122
+ * device is still. `positions` is empty when the update has no new position.
120
123
  */
121
124
  readonly stationary?: boolean;
122
125
  }