@wayq/beekon-rn 0.0.5 → 0.0.6

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.
Files changed (59) hide show
  1. package/BeekonRn.podspec +4 -2
  2. package/CHANGELOG.md +103 -0
  3. package/README.md +303 -81
  4. package/android/build.gradle +1 -1
  5. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +113 -7
  6. package/ios/BeekonRn.mm +13 -0
  7. package/ios/BeekonRn.swift +113 -9
  8. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
  9. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  10. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +5399 -1463
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +77 -2
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +5399 -1463
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +77 -2
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5399 -1463
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +77 -2
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
  23. package/lib/module/NativeBeekonRn.js +13 -0
  24. package/lib/module/NativeBeekonRn.js.map +1 -1
  25. package/lib/module/beekon.js +56 -7
  26. package/lib/module/beekon.js.map +1 -1
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/internal/mappers.js +50 -3
  29. package/lib/module/internal/mappers.js.map +1 -1
  30. package/lib/module/types/auth.js +4 -0
  31. package/lib/module/types/auth.js.map +1 -0
  32. package/lib/module/types/error.js +13 -3
  33. package/lib/module/types/error.js.map +1 -1
  34. package/lib/typescript/src/NativeBeekonRn.d.ts +48 -0
  35. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  36. package/lib/typescript/src/beekon.d.ts +30 -1
  37. package/lib/typescript/src/beekon.d.ts.map +1 -1
  38. package/lib/typescript/src/index.d.ts +2 -1
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/internal/mappers.d.ts +5 -1
  41. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  42. package/lib/typescript/src/types/auth.d.ts +99 -0
  43. package/lib/typescript/src/types/auth.d.ts.map +1 -0
  44. package/lib/typescript/src/types/config.d.ts +16 -0
  45. package/lib/typescript/src/types/config.d.ts.map +1 -1
  46. package/lib/typescript/src/types/enums.d.ts +14 -0
  47. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  48. package/lib/typescript/src/types/error.d.ts +14 -4
  49. package/lib/typescript/src/types/error.d.ts.map +1 -1
  50. package/package.json +5 -1
  51. package/scripts/fetch-beekonkit.sh +4 -4
  52. package/src/NativeBeekonRn.ts +55 -0
  53. package/src/beekon.ts +66 -6
  54. package/src/index.tsx +3 -0
  55. package/src/internal/mappers.ts +59 -1
  56. package/src/types/auth.ts +101 -0
  57. package/src/types/config.ts +16 -0
  58. package/src/types/enums.ts +16 -0
  59. package/src/types/error.ts +19 -4
package/BeekonRn.podspec CHANGED
@@ -10,8 +10,10 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
- # BeekonKit targets iOS 17.0 (uses CLLocationUpdate.liveUpdates). The
14
- # consuming app must also target iOS 17.0 or higher.
13
+ # iOS 17.0 floor reflects this wrapper's React Native baseline (RN 0.85), not
14
+ # a BeekonKit constraint BeekonKit 0.0.6 itself supports iOS 13+ (with
15
+ # 13–16 fallback paths). Kept at 17.0 to avoid any consumer regression; do not
16
+ # lower below React Native's own minimum.
15
17
  s.platforms = { :ios => "17.0" }
16
18
  s.source = { :git => "https://github.com/wayqteam/beekon-rn.git", :tag => "v#{s.version}" }
17
19
 
package/CHANGELOG.md ADDED
@@ -0,0 +1,103 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@wayq/beekon-rn` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ Beekon is pre-1.0 — releases may contain breaking changes.
8
+
9
+ ## [0.0.6]
10
+
11
+ Built against the native **0.0.6** API; requires native ≥ 0.0.6 at runtime.
12
+
13
+ ### Added
14
+
15
+ - **Native token refresh** — `SyncConfig.auth` (`AuthConfig`) lets the SDK attach
16
+ and natively refresh the upload access token, proactively before expiry and
17
+ reactively on a `401`/`403`, and retry the upload — all natively, so it works in
18
+ the background and on a cold launch with no host involvement. Rotated credentials
19
+ surface on the new `Beekon.onAuthTokens()` subscription (`AuthTokens`). Adds the
20
+ `AuthConfig`, `AuthResponseMapping`, `AuthTokens` types and the `AuthStrategy` /
21
+ `AuthBodyFormat` enums. Omitting `auth` keeps the legacy static-`headers`
22
+ behaviour. The refresh recipe is persisted in plaintext to survive cold launch —
23
+ do not put static client secrets in `refreshHeaders`/`refreshPayload`.
24
+ - **`getCurrentLocation({ timeoutMs, accuracy })`** — a one-shot current location,
25
+ independent of any tracking session (resolves `null` on timeout; never starts or
26
+ stops tracking; the fix is not persisted and does not appear on `onLocation`).
27
+ Re-introduces thrown `BeekonError` kinds `'permissionDenied'`,
28
+ `'locationServicesDisabled'`, and `'locationUnavailable'` on a precondition
29
+ failure (0.0.5 had removed the `PERMISSION_DENIED` / `LOCATION_SERVICES_DISABLED`
30
+ thrown errors; they now surface again, but only from `getCurrentLocation()` —
31
+ tracking-lifecycle problems still report through `onState` as `Stopped(reason)`).
32
+ - **`NotificationConfig.smallIcon`** — a custom Android status-bar icon (drawable /
33
+ mipmap resource name) for the foreground-service tracking notification.
34
+
35
+ ### Changed
36
+
37
+ - `androidNotification` (type `AndroidNotificationConfig`) renamed to `notification`
38
+ (type `NotificationConfig`) — **breaking**. The config remains Android-only; iOS
39
+ ignores it.
40
+ - `deleteLocations(before)` cutoff is now strictly **before** `before` (exclusive
41
+ `<`), aligning both natives and all wrappers; the prior wording said "at or
42
+ before".
43
+ - `resumeIfNeeded()` now calls the guarded native `Beekon.resumeIfNeeded()` on
44
+ Android (previously `Beekon.start()`): it re-adopts a previously-active,
45
+ non-user-stopped session only, mirroring iOS.
46
+ - Native pins bumped to `0.0.6` (Maven `io.github.wayqteam:beekon`, iOS
47
+ `BeekonKit.xcframework`).
48
+
49
+ ## [0.0.5]
50
+
51
+ Full rebuild against the native **0.0.5** API — a breaking, no-backward-compat
52
+ release. The TypeScript surface is a faithful 1:1 mirror of the native `Beekon`
53
+ (Android) / `Beekon.shared` (iOS).
54
+
55
+ ### Added
56
+
57
+ - **Server sync**: `SyncConfig` on `BeekonConfig`, plus `sync()`, `setExtras()`,
58
+ `pendingUploadCount()`, and the `onSyncStatus` subscription
59
+ (`SyncIdle`/`SyncPending`/`SyncFailed`).
60
+ - **Geofencing**: `addGeofences()` / `removeGeofences()` / `listGeofences()` and
61
+ the `onGeofenceEvent` subscription (`BeekonGeofence`, `GeofenceEvent`,
62
+ `Transition`).
63
+ - **Richer config**: `accuracyMode`, `whenStationary`, `stationaryRadiusMeters`,
64
+ `detectActivity`.
65
+ - **Richer `Location`**: `id`, `quality`, `trigger`, `motion`, `activity`,
66
+ `isMock`.
67
+ - `deleteLocations(before)`, `resumeIfNeeded()` (wrapper-only cold-launch resume,
68
+ not part of the native spec), and `StopReason 'locationUnavailable'`.
69
+
70
+ ### Changed
71
+
72
+ - `intervalSeconds` → `minTimeBetweenLocationsSeconds`; `distanceMeters` →
73
+ `minDistanceBetweenLocationsMeters`.
74
+ - `getLocations(from: Date, to: Date)` replaces the prior history call.
75
+ - Notification config is now `androidNotification` (Android-only; iOS ignores it).
76
+ - Native pins bumped to `0.0.5` (Maven `io.github.wayqteam:beekon`, SwiftPM
77
+ `beekon-ios-binary`).
78
+
79
+ ### Removed
80
+
81
+ - `PERMISSION_DENIED` / `LOCATION_SERVICES_DISABLED` thrown errors — permission
82
+ and service problems now surface only on `onState` as `Stopped(reason)`.
83
+
84
+ ## [0.0.3]
85
+
86
+ First synchronized release across all four registries (Maven Central,
87
+ `wayqteam/beekon-ios-binary` xcframework, pub.dev, npm). `Location`
88
+ accuracy/speed/bearing/altitude are nullable to faithfully represent providers
89
+ that don't deliver them.
90
+
91
+ ## [0.0.1]
92
+
93
+ Initial release.
94
+
95
+ ### Added
96
+
97
+ - React Native (New Architecture / TurboModule) binding for the native Beekon
98
+ location SDKs (Android `io.github.wayqteam:beekon`, iOS `BeekonKit`
99
+ xcframework).
100
+ - Public API mirroring the native surface: `Beekon.configure / start / stop /
101
+ getLocations`, plus `onState` / `onLocation` subscriptions.
102
+ - `BeekonConfig`, `BeekonState` (`idle → tracking → stopped(reason)`),
103
+ `Location`, and typed `BeekonError` kinds.
package/README.md CHANGED
@@ -1,43 +1,70 @@
1
- # @wayq/beekon-rn
1
+ <p align="center">
2
+ <img src="assets/icon.png" alt="Beekon" width="140" />
3
+ </p>
2
4
 
3
- React Native binding for the [Beekon](https://github.com/wayqteam/beekon) location SDK.
5
+ <h1 align="center">@wayq/beekon-rn</h1>
4
6
 
5
- A thin pass-through over the native Android (`io.github.wayqteam:beekon`) and iOS (`BeekonKit`) libraries at version **0.0.5**. The public TypeScript surface mirrors the Kotlin / Swift APIs in shape: a 12-method `Beekon` singleton, four callback streams (`state`, `locations`, `geofenceEvents`, `syncStatus`), geofencing, and optional server sync.
7
+ <p align="center">
8
+ Background location tracking for React Native.<br/>
9
+ Native location stack on each platform — tracking survives backgrounding and termination.
10
+ </p>
6
11
 
7
- Built on the React Native New Architecture (TurboModules + Codegen). The binding contains no location logic — all tracking, persistence, geofencing, and OS integration live natively. **Writes never cross into JavaScript**: in the background the JS engine isn't guaranteed to be alive, so the native libraries own the persistence path end-to-end.
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@wayq/beekon-rn"><img alt="npm" src="https://img.shields.io/npm/v/@wayq/beekon-rn?label=npm&logo=npm&logoColor=white&color=CB3837&style=flat-square"></a>
14
+ <img alt="React Native · New Architecture" src="https://img.shields.io/badge/React%20Native-New%20Arch-61DAFB?logo=react&logoColor=white&style=flat-square">
15
+ <img alt="Platforms · Android | iOS" src="https://img.shields.io/badge/platforms-Android%20%7C%20iOS-61DAFB?style=flat-square">
16
+ </p>
8
17
 
9
- There is **no `initialize()`** — the native SDKs auto-initialize. `start()` / `stop()` **never throw**: subscribe to `onState` to learn whether tracking is active and why it stopped.
18
+ <p align="center">
19
+ <a href="https://docs.getbeekon.com"><strong>Docs</strong></a> ·
20
+ <a href="https://console.getbeekon.com"><strong>Console</strong></a> ·
21
+ <a href="https://getbeekon.com"><strong>getbeekon.com</strong></a> ·
22
+ <a href="https://github.com/wayqteam/beekon"><strong>Umbrella</strong></a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ Capture GPS in the foreground and background, keep a queryable history on the device, define geofences, and optionally sync to your own server. The JavaScript API is fully typed.
28
+
29
+ ## Features
30
+
31
+ - Foreground and background tracking that survives app termination
32
+ - Battery-aware capture: time and distance filtering, accuracy presets, and automatic pausing while the device is stationary
33
+ - On-device history you can query at any time
34
+ - Geofencing with enter and exit events
35
+ - Optional batched server sync with automatic retry
36
+ - Activity detection (walking, running, cycling, automotive)
37
+ - Built for the React Native New Architecture
10
38
 
11
39
  ## Requirements
12
40
 
13
- | Area | Floor |
41
+ | | Minimum |
14
42
  |---|---|
15
- | React Native | 0.76+ (target 0.85) |
43
+ | React Native | 0.76 (New Architecture) |
16
44
  | iOS | 17.0 |
17
- | Android | **API 26** (required by the native AAR) |
18
- | New Architecture | required (Old Arch is unsupported) |
45
+ | Android | 8.0 (API level 26) |
19
46
 
20
- ## Install
47
+ ## Installation
21
48
 
22
49
  ```sh
23
50
  npm install @wayq/beekon-rn
24
- # or
25
- yarn add @wayq/beekon-rn
26
51
  ```
27
52
 
28
- iOS — the package fetches and bundles `BeekonKit.xcframework` (SHA256-verified) during `prepare`, so just install pods:
53
+ **iOS** — install pods:
29
54
 
30
55
  ```sh
31
56
  cd ios && pod install
32
57
  ```
33
58
 
34
- Android — Maven Central is auto-included by the autolinker; the native AAR (`io.github.wayqteam:beekon`) is pulled transitively.
59
+ **Android**no additional steps; the module links automatically.
60
+
61
+ ## Permissions and setup
35
62
 
36
- ## Permissions
63
+ Beekon does not request permissions on your behalf. Declare them in your app and request them at runtime (for example with `PermissionsAndroid` or `react-native-permissions`).
37
64
 
38
- The library does NOT request permissions — your app must (e.g. via [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) or `PermissionsAndroid`). Foreground location works without background location.
65
+ ### Android
39
66
 
40
- **Android** (`AndroidManifest.xml`):
67
+ In `android/app/src/main/AndroidManifest.xml`:
41
68
 
42
69
  ```xml
43
70
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@@ -45,20 +72,21 @@ The library does NOT request permissions — your app must (e.g. via [`react-nat
45
72
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
46
73
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
47
74
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
48
- <!-- Only when BeekonConfig.detectActivity is enabled -->
75
+ <!-- Only if you enable detectActivity -->
49
76
  <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
50
77
  ```
51
78
 
52
- **iOS** (`Info.plist`):
79
+ While tracking, Android shows a persistent notification (required by the system). Set its title and text with the `notification` option.
80
+
81
+ ### iOS
82
+
83
+ In `Info.plist`:
53
84
 
54
85
  ```xml
55
86
  <key>NSLocationWhenInUseUsageDescription</key>
56
- <string>…</string>
87
+ <string>Explain why your app uses location.</string>
57
88
  <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
58
- <string>…</string>
59
- <!-- Only when BeekonConfig.detectActivity is enabled -->
60
- <key>NSMotionUsageDescription</key>
61
- <string>…</string>
89
+ <string>Explain why your app uses location in the background.</string>
62
90
  <key>UIBackgroundModes</key>
63
91
  <array>
64
92
  <string>location</string>
@@ -68,101 +96,295 @@ The library does NOT request permissions — your app must (e.g. via [`react-nat
68
96
  <array>
69
97
  <string>in.wayq.beekon.sync</string>
70
98
  </array>
99
+ <!-- Only if you enable detectActivity -->
100
+ <key>NSMotionUsageDescription</key>
101
+ <string>Explain why your app detects activity.</string>
71
102
  ```
72
103
 
73
- ## Background-task registration (iOS)
74
-
75
- For background sync scheduling and cold-launch resume, register Beekon's background task **synchronously during app launch** (it must run before `didFinishLaunchingWithOptions` returns). In your `AppDelegate.swift`:
104
+ Register Beekon's background task once during launch (required for background sync and to resume tracking after the app is terminated). In `AppDelegate.swift`:
76
105
 
77
106
  ```swift
78
107
  import BeekonRn
79
108
 
80
- func application(_ application: UIApplication,
81
- didFinishLaunchingWithOptions launchOptions: …) -> Bool {
109
+ func application(
110
+ _ application: UIApplication,
111
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
112
+ ) -> Bool {
82
113
  BeekonRnImpl.registerBackgroundTasks()
83
- // React Native setup
114
+ // ...your existing setup...
84
115
  return true
85
116
  }
86
117
  ```
87
118
 
88
- Foreground tracking and the JS streams work without this; only background sync / relaunch need it.
119
+ ## Usage
89
120
 
90
- ## Cold-launch resume (Android)
121
+ ### Start tracking
91
122
 
92
- To resume tracking after the OS killed the process, call `in.wayq.beekon.Beekon.start()` from your `Application.onCreate` (native — the JS engine may not be running on a background relaunch). From JS, `Beekon.resumeIfNeeded()` covers the foreground case.
123
+ ```ts
124
+ import { Beekon } from '@wayq/beekon-rn';
93
125
 
94
- ## Usage
126
+ const offState = Beekon.onState((state) => {
127
+ console.log('state:', state.kind); // 'idle' | 'tracking' | 'stopped'
128
+ });
129
+
130
+ const offLocation = Beekon.onLocation((location) => {
131
+ console.log(location.latitude, location.longitude, location.timestamp);
132
+ });
133
+
134
+ // Configure is optional. Request OS permissions before starting.
135
+ await Beekon.configure({
136
+ minTimeBetweenLocationsSeconds: 30,
137
+ minDistanceBetweenLocationsMeters: 100,
138
+ });
139
+
140
+ await Beekon.start();
141
+
142
+ // Later:
143
+ await Beekon.stop();
144
+ offState();
145
+ offLocation();
146
+ ```
147
+
148
+ `start()` and `stop()` never throw. Observe `onState` to learn whether tracking began and why it stopped:
149
+
150
+ ```ts
151
+ Beekon.onState((state) => {
152
+ if (state.kind === 'stopped') {
153
+ // 'user' | 'permissionDenied' | 'locationServicesDisabled'
154
+ // | 'locationUnavailable' | 'system'
155
+ console.log('stopped:', state.reason);
156
+ }
157
+ });
158
+ ```
159
+
160
+ ### Read history
161
+
162
+ Recorded fixes are stored on the device and remain available even after the app is closed.
163
+
164
+ ```ts
165
+ const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
166
+ const fixes = await Beekon.getLocations(since, new Date());
167
+
168
+ await Beekon.deleteLocations(); // delete everything
169
+ await Beekon.deleteLocations(since); // delete up to a cutoff
170
+ ```
171
+
172
+ ### Geofences
95
173
 
96
174
  ```ts
97
- import { Beekon, type BeekonState, type Location } from '@wayq/beekon-rn';
175
+ await Beekon.addGeofences([
176
+ { id: 'office', latitude: 37.331, longitude: -122.031, radiusMeters: 150 },
177
+ ]);
178
+
179
+ const offGeofence = Beekon.onGeofenceEvent((event) => {
180
+ console.log(event.geofenceId, event.type); // 'enter' | 'exit'
181
+ });
182
+
183
+ await Beekon.listGeofences();
184
+ await Beekon.removeGeofences(['office']);
185
+ ```
98
186
 
99
- // Subscribe — each returns an unsubscribe function. `onState` / `onSyncStatus`
100
- // replay the latest value to new subscribers; `onLocation` / `onGeofenceEvent`
101
- // are broadcast (no replay).
102
- const offState = Beekon.onState((s: BeekonState) => console.log('state', s));
103
- const offLoc = Beekon.onLocation((l: Location) => console.log('location', l));
187
+ ### Server sync
104
188
 
105
- // Configure (optional; defaults below). Pass 0 to disable a gate.
189
+ Add a `sync` config to upload recorded fixes and geofence events to your backend. Uploads are batched, retried automatically, and removed from the device once your server accepts them. Without a `sync` config, all data stays on the device.
190
+
191
+ ```ts
106
192
  await Beekon.configure({
107
- minTimeBetweenLocationsSeconds: 30, // default 30
108
- minDistanceBetweenLocationsMeters: 100, // default 100
109
- accuracyMode: 'balanced', // 'high' | 'balanced' | 'low'
110
- whenStationary: 'pause', // 'keepTracking' | 'pause' | 'pauseWithCheckIns'
111
- detectActivity: false,
112
- // Optional server upload:
113
193
  sync: {
114
- url: 'https://example.com/ingest',
115
- headers: { Authorization: 'Bearer ' },
194
+ url: 'https://api.example.com/locations',
195
+ headers: { Authorization: 'Bearer <token>' },
196
+ // intervalSeconds defaults to 300, batchSize to 100
116
197
  },
117
- // Android-only foreground-service notification:
118
- notification: { title: 'Tracking', text: 'Recording your route' },
119
198
  });
120
199
 
121
- await Beekon.start(); // never throws observe onState
122
- // … later
123
- await Beekon.stop();
200
+ Beekon.setExtras({ userId: 'abc123' }); // attached to every upload
201
+
202
+ Beekon.onSyncStatus((status) => {
203
+ console.log('sync:', status.kind); // 'idle' | 'pending' | 'failed'
204
+ });
205
+ ```
124
206
 
125
- // History (local store; source of truth even when JS was asleep).
126
- const fixes = await Beekon.getLocations(new Date(Date.now() - 3600_000), new Date());
207
+ #### Token refresh
127
208
 
128
- // Cleanup.
129
- offState();
130
- offLoc();
209
+ Set `sync.auth` (an `AuthConfig`) to let the SDK attach and **natively** refresh the upload access token — proactively before it expires and reactively on a `401`/`403`, then retry the upload. This runs in the background and survives a cold launch with no host involvement. Without `auth`, a `401`/`403` pauses sync and surfaces `SyncFailure 'auth'`; re-seed by calling `configure()` again to resume.
210
+
211
+ The supplied tokens are a **seed**: the SDK owns and rotates the live token set in secure storage (Keychain / encrypted prefs) after first persist. Subscribe with `onAuthTokens` to mirror rotations into your own session store.
212
+
213
+ ```ts
214
+ await Beekon.configure({
215
+ sync: {
216
+ url: 'https://api.example.com/locations',
217
+ auth: {
218
+ accessToken: '<initial access token>',
219
+ refreshToken: '<initial refresh token>',
220
+ expiresAt: new Date(Date.now() + 3600_000),
221
+ refreshUrl: 'https://auth.example.com/oauth/token',
222
+ refreshPayload: {
223
+ grant_type: 'refresh_token',
224
+ refresh_token: '{refreshToken}', // substituted with the current token
225
+ },
226
+ // strategy defaults to 'bearer', refreshBodyFormat to 'form',
227
+ // skewMarginSeconds to 60. responseMapping defaults to common-name detection.
228
+ },
229
+ },
230
+ });
231
+
232
+ const offAuth = Beekon.onAuthTokens((tokens) => {
233
+ // Persist the rotated set into your own session store.
234
+ console.log('rotated:', tokens.accessToken, tokens.expiresAt, tokens.epoch);
235
+ });
131
236
  ```
132
237
 
238
+ The refresh recipe is persisted in plaintext to survive a cold launch — **do not** put static client secrets in `refreshHeaders` or `refreshPayload`. Requires the native SDK ≥ 0.0.6; with an older native binary the recipe is ignored and only static `sync.headers` apply.
239
+
240
+ ## Configuration
241
+
242
+ All options are optional; defaults are shown.
243
+
244
+ | Option | Default | Description |
245
+ |---|---|---|
246
+ | `minTimeBetweenLocationsSeconds` | `30` | Minimum seconds between recorded fixes. `0` disables the time filter. |
247
+ | `minDistanceBetweenLocationsMeters` | `100` | Minimum metres between recorded fixes. `0` disables the distance filter. |
248
+ | `accuracyMode` | `'balanced'` | `'high'` \| `'balanced'` \| `'low'`. Trades accuracy against battery. |
249
+ | `whenStationary` | `'pause'` | `'keepTracking'` \| `'pause'` \| `'pauseWithCheckIns'`. |
250
+ | `stationaryRadiusMeters` | `5` | Distance the device must move to count as moving again. |
251
+ | `detectActivity` | `false` | Detect physical activity. Requires the motion permission. |
252
+ | `sync` | – | Server upload settings: `{ url, headers?, intervalSeconds?, batchSize?, auth? }`. See [Token refresh](#token-refresh). |
253
+ | `notification` | – | Android-only tracking notification: `{ title?, text?, smallIcon? }`. `smallIcon` is a drawable/mipmap resource name. |
254
+
255
+ A fix is recorded only when **both** the time and distance filters are satisfied.
256
+
133
257
  ## API
134
258
 
135
259
  | Method | Description |
136
260
  |---|---|
137
- | `configure(config)` | Set config. Optional, idempotent. Does not throw. |
138
- | `start()` | Begin tracking. Never throws — observe `onState`. |
139
- | `stop()` | Stop tracking. Idempotent. Never throws. |
140
- | `resumeIfNeeded()` | Resume a session active before the app was terminated. |
141
- | `getLocations(from, to)` | Read persisted fixes in `[from, to]`. Throws `'storage'`. |
142
- | `deleteLocations(before?)` | Delete fixes at/before `before` (all when omitted). Throws `'storage'`. |
143
- | `pendingUploadCount()` | Count of fixes not yet uploaded. Throws `'storage'`. |
144
- | `sync()` | Request an immediate upload (no-op if sync unconfigured). |
145
- | `setExtras(extras)` | Custom key/value fields sent with every upload. |
146
- | `addGeofences(geofences)` | Register geofences. Throws `'invalidGeofence'`. |
261
+ | `configure(config)` | Apply settings. Optional; may be called while tracking. |
262
+ | `start()` | Begin tracking. |
263
+ | `stop()` | Stop tracking. |
264
+ | `resumeIfNeeded()` | Resume a session that was active before the app closed. |
265
+ | `getCurrentLocation(options?)` | One-shot current fix, independent of tracking. Resolves `null` on timeout. `options`: `{ timeoutMs?, accuracy? }`. |
266
+ | `getLocations(from, to)` | Recorded fixes within a date range. |
267
+ | `deleteLocations(before?)` | Delete fixes (all if `before` is omitted); returns the count removed. |
268
+ | `pendingUploadCount()` | Number of fixes not yet uploaded. |
269
+ | `sync()` | Request an immediate upload. |
270
+ | `setExtras(extras)` | Custom fields included with every upload. |
271
+ | `addGeofences(geofences)` | Register circular regions. |
147
272
  | `removeGeofences(ids)` | Unregister geofences by id. |
148
- | `listGeofences()` | The currently registered geofences. |
149
- | `onState(cb)` | Subscribe to state. Replay-1. Returns unsubscribe fn. |
150
- | `onLocation(cb)` | Subscribe to gated fixes. No replay. |
151
- | `onGeofenceEvent(cb)` | Subscribe to geofence enter/exit. No replay. |
152
- | `onSyncStatus(cb)` | Subscribe to upload health. Replay-1. |
273
+ | `listGeofences()` | Currently registered geofences. |
153
274
 
154
- `BeekonState` is a discriminated union on `kind`: `'idle' | 'tracking' | 'stopped'`. The `'stopped'` variant carries `reason: 'user' | 'permissionDenied' | 'locationServicesDisabled' | 'locationUnavailable' | 'system'`.
275
+ Subscriptions each returns an unsubscribe function:
155
276
 
156
- `SyncStatus` is a union on `kind`: `'idle' | 'pending' | 'failed'`; `'failed'` carries `reason: 'auth' | 'rejected'`.
277
+ | Subscription | Delivers |
278
+ |---|---|
279
+ | `onState(cb)` | Tracking state changes. The current state is delivered immediately. |
280
+ | `onLocation(cb)` | Each newly recorded fix. |
281
+ | `onGeofenceEvent(cb)` | Geofence enter and exit events. |
282
+ | `onSyncStatus(cb)` | Upload status. The current status is delivered immediately. |
283
+ | `onAuthTokens(cb)` | Token rotations from native refresh (see [Token refresh](#token-refresh)). The latest rotation is delivered immediately. |
157
284
 
158
- `Location` carries `id`, `latitude`, `longitude`, `timestamp`, the nullable `accuracy` / `speed` / `bearing` / `altitude`, plus `quality`, `trigger`, `motion`, `activity` (`null` unless `detectActivity`), and `isMock`. Optional numeric fields are `null` when the OS did not report a value never `0`.
285
+ `getLocations`, `deleteLocations`, `pendingUploadCount`, and `addGeofences` reject with a `BeekonError` on failure check `error.kind` (`'storage'` or `'invalidGeofence'`). `getCurrentLocation` rejects with `error.kind` `'permissionDenied'`, `'locationServicesDisabled'`, or `'locationUnavailable'` on a precondition failure. Other methods resolve normally; tracking-lifecycle problems are reported through `onState` rather than thrown.
159
286
 
160
- The **only** thrown errors are `BeekonError` instances with `kind: 'storage' | 'invalidGeofence'`. Permission / services / lifecycle problems are not thrown — they surface on `onState` as `stopped(reason)`.
287
+ ## Types
161
288
 
162
- ## Storage, retention, and sync
289
+ ```ts
290
+ type Location = {
291
+ id: string;
292
+ latitude: number;
293
+ longitude: number;
294
+ timestamp: Date;
295
+ accuracy: number | null; // metres
296
+ speed: number | null; // m/s
297
+ bearing: number | null; // degrees from true north
298
+ altitude: number | null; // metres
299
+ quality: 'ok' | 'lowAccuracy' | 'implausibleSpeed';
300
+ trigger: 'interval' | 'motion' | 'checkIn' | 'geofence' | 'manual';
301
+ motion: 'moving' | 'stationary' | 'unknown';
302
+ activity:
303
+ | 'stationary' | 'walking' | 'running'
304
+ | 'cycling' | 'automotive' | 'unknown' | null;
305
+ isMock: boolean;
306
+ };
307
+
308
+ type BeekonState =
309
+ | { kind: 'idle' }
310
+ | { kind: 'tracking' }
311
+ | {
312
+ kind: 'stopped';
313
+ reason:
314
+ | 'user' | 'permissionDenied' | 'locationServicesDisabled'
315
+ | 'locationUnavailable' | 'system';
316
+ };
317
+
318
+ type SyncStatus =
319
+ | { kind: 'idle' }
320
+ | { kind: 'pending' }
321
+ | { kind: 'failed'; reason: 'auth' | 'rejected' };
322
+
323
+ type BeekonGeofence = {
324
+ id: string;
325
+ latitude: number;
326
+ longitude: number;
327
+ radiusMeters: number;
328
+ notifyOnEntry?: boolean; // default true
329
+ notifyOnExit?: boolean; // default true
330
+ };
331
+
332
+ type GeofenceEvent = {
333
+ id: string;
334
+ geofenceId: string;
335
+ type: 'enter' | 'exit';
336
+ timestamp: Date;
337
+ };
338
+
339
+ // Set on `SyncConfig.auth` to enable native token refresh. The token fields are
340
+ // a seed — the SDK rotates them after first persist.
341
+ type AuthConfig = {
342
+ accessToken?: string;
343
+ refreshToken?: string;
344
+ expiresAt?: Date;
345
+ strategy?: 'bearer' | 'raw'; // default 'bearer'
346
+ refreshUrl?: string; // omit to disable refresh
347
+ refreshPayload?: Record<string, string>; // '{refreshToken}' substituted
348
+ refreshHeaders?: Record<string, string>; // '{accessToken}' substituted
349
+ refreshBodyFormat?: 'form' | 'json'; // default 'form'
350
+ responseMapping?: {
351
+ accessToken?: string;
352
+ refreshToken?: string;
353
+ expiresIn?: string;
354
+ expiresAt?: string;
355
+ };
356
+ skewMarginSeconds?: number; // default 60
357
+ seedEpoch?: number;
358
+ };
359
+
360
+ // Delivered by `onAuthTokens` after each native rotation. Sensitive — in-process
361
+ // only, never logged.
362
+ type AuthTokens = {
363
+ accessToken: string;
364
+ refreshToken: string | null;
365
+ expiresAt: Date | null;
366
+ epoch: number; // monotonic generation, bumped on each refresh
367
+ };
368
+ ```
369
+
370
+ Optional numeric fields on `Location` are `null` when the device did not report them — never `0`.
371
+
372
+ ## Notes
163
373
 
164
- The native SDKs persist every gated fix locally (Room on Android, GRDB on iOS) JS is a passive reader. Retention: **TTL 7 days OR the most recent 100 K rows**, whichever is smaller; auto-pruned on each write batch. With `sync` configured, accepted rows are deleted locally after upload, so `getLocations` / `pendingUploadCount` return only un-uploaded fixes.
374
+ - **Retention.** Fixes are kept on the device for up to 7 days, or the most recent 100,000 fixes, whichever comes first.
375
+ - **Background relaunch.** Call `resumeIfNeeded()` early in your app to restore tracking after the system terminates it. On Android, also resume from your `Application.onCreate` (see the example app).
376
+ - **Android background limits.** Some manufacturers aggressively stop background services; if tracking halts unexpectedly, see [dontkillmyapp.com](https://dontkillmyapp.com).
377
+
378
+ ## Example
379
+
380
+ A complete, runnable example is in the [`example/`](./example) directory.
165
381
 
166
382
  ## License
167
383
 
168
- Proprietary — see [LICENSE.txt](LICENSE.txt). Evaluation use only without a separate written agreement with wayqteam.
384
+ Proprietary — evaluation use only without a separate written agreement with wayqteam. See [LICENSE.txt](./LICENSE.txt).
385
+
386
+ ---
387
+
388
+ <p align="center">
389
+ © WayQ Technologies Pvt Ltd
390
+ </p>
@@ -77,7 +77,7 @@ dependencies {
77
77
  // Native Beekon SDK — published from beekon-android/ via Maven Central.
78
78
  // Pinned exact in v0.x to avoid surprise breakage; loosen to a range when
79
79
  // the SDK reaches v1 stability.
80
- implementation "io.github.wayqteam:beekon:0.0.5"
80
+ implementation "io.github.wayqteam:beekon:0.0.6"
81
81
  // Kotlin coroutines — required for collecting Beekon's StateFlow/SharedFlow.
82
82
  // Beekon already depends on coroutines transitively, but declaring it here
83
83
  // makes the dependency intent explicit.