@wayq/beekon-rn 0.0.1 → 0.0.5

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 (92) hide show
  1. package/BeekonRn.podspec +4 -4
  2. package/README.md +94 -48
  3. package/android/build.gradle +11 -6
  4. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +428 -0
  5. package/android/src/main/java/{com → in}/wayq/beekonrn/BeekonRnPackage.kt +1 -1
  6. package/ios/BeekonRn.h +5 -1
  7. package/ios/BeekonRn.mm +90 -34
  8. package/ios/BeekonRn.swift +396 -116
  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 +8636 -0
  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 +236 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/PrivacyInfo.xcprivacy +1 -1
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +8636 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +236 -0
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +8636 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +236 -0
  23. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/PrivacyInfo.xcprivacy +1 -1
  24. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +2 -14
  25. package/lib/module/NativeBeekonRn.js +22 -7
  26. package/lib/module/NativeBeekonRn.js.map +1 -1
  27. package/lib/module/beekon.js +209 -60
  28. package/lib/module/beekon.js.map +1 -1
  29. package/lib/module/index.js +1 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/internal/mappers.js +145 -23
  32. package/lib/module/internal/mappers.js.map +1 -1
  33. package/lib/module/types/enums.js +2 -0
  34. package/lib/module/types/{preset.js.map → enums.js.map} +1 -1
  35. package/lib/module/types/error.js +25 -0
  36. package/lib/module/types/error.js.map +1 -0
  37. package/lib/module/types/geofence.js +2 -0
  38. package/lib/module/types/{position.js.map → geofence.js.map} +1 -1
  39. package/lib/module/types/location.js +4 -0
  40. package/lib/module/types/location.js.map +1 -0
  41. package/lib/module/types/sync.js +2 -0
  42. package/lib/module/types/sync.js.map +1 -0
  43. package/lib/typescript/src/NativeBeekonRn.d.ts +113 -35
  44. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  45. package/lib/typescript/src/beekon.d.ts +84 -49
  46. package/lib/typescript/src/beekon.d.ts.map +1 -1
  47. package/lib/typescript/src/index.d.ts +7 -4
  48. package/lib/typescript/src/index.d.ts.map +1 -1
  49. package/lib/typescript/src/internal/mappers.d.ts +16 -3
  50. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  51. package/lib/typescript/src/types/config.d.ts +53 -31
  52. package/lib/typescript/src/types/config.d.ts.map +1 -1
  53. package/lib/typescript/src/types/enums.d.ts +48 -0
  54. package/lib/typescript/src/types/enums.d.ts.map +1 -0
  55. package/lib/typescript/src/types/error.d.ts +20 -0
  56. package/lib/typescript/src/types/error.d.ts.map +1 -0
  57. package/lib/typescript/src/types/geofence.d.ts +36 -0
  58. package/lib/typescript/src/types/geofence.d.ts.map +1 -0
  59. package/lib/typescript/src/types/location.d.ts +40 -0
  60. package/lib/typescript/src/types/location.d.ts.map +1 -0
  61. package/lib/typescript/src/types/state.d.ts +18 -9
  62. package/lib/typescript/src/types/state.d.ts.map +1 -1
  63. package/lib/typescript/src/types/sync.d.ts +27 -0
  64. package/lib/typescript/src/types/sync.d.ts.map +1 -0
  65. package/package.json +5 -6
  66. package/scripts/fetch-beekonkit.sh +5 -2
  67. package/src/NativeBeekonRn.ts +120 -34
  68. package/src/beekon.ts +235 -63
  69. package/src/index.tsx +23 -4
  70. package/src/internal/mappers.ts +213 -22
  71. package/src/types/config.ts +54 -31
  72. package/src/types/enums.ts +64 -0
  73. package/src/types/error.ts +25 -0
  74. package/src/types/geofence.ts +37 -0
  75. package/src/types/location.ts +45 -0
  76. package/src/types/state.ts +23 -7
  77. package/src/types/sync.ts +23 -0
  78. package/android/src/main/java/com/wayq/beekonrn/BeekonRnModule.kt +0 -233
  79. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeDirectory +0 -0
  80. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements +0 -0
  81. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
  82. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeResources +0 -233
  83. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeSignature +0 -0
  84. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/_CodeSignature/CodeResources +0 -113
  85. package/lib/module/types/position.js +0 -2
  86. package/lib/module/types/preset.js +0 -2
  87. package/lib/typescript/src/types/position.d.ts +0 -24
  88. package/lib/typescript/src/types/position.d.ts.map +0 -1
  89. package/lib/typescript/src/types/preset.d.ts +0 -12
  90. package/lib/typescript/src/types/preset.d.ts.map +0 -1
  91. package/src/types/position.ts +0 -23
  92. package/src/types/preset.ts +0 -11
@@ -13,9 +13,12 @@
13
13
 
14
14
  set -euo pipefail
15
15
 
16
- VERSION="0.0.1"
16
+ VERSION="0.0.5"
17
17
  URL="https://github.com/wayqteam/beekon-ios-binary/releases/download/v${VERSION}/BeekonKit.xcframework.zip"
18
- EXPECTED_SHA="f7bd79a6d61994977318daa6d722866abd0a61d7d7e5834ab5307df39b6ee60e"
18
+ # SHA256 of the v0.0.5 BeekonKit.xcframework.zip. Matches the SwiftPM
19
+ # `binaryTarget` checksum in beekon-ios-binary's Package.swift at tag v0.0.5
20
+ # (SwiftPM's compute-checksum is the SHA256 of the zip).
21
+ EXPECTED_SHA="437bf493d7de19d8df9599da097be162796a8304eb2d7826cf2505c33d4603f7"
19
22
 
20
23
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
21
24
  DEST_DIR="${ROOT}/ios/Frameworks"
@@ -1,64 +1,150 @@
1
1
  /**
2
- * Codegen TurboModule spec — DO NOT export Wire* types from `src/index.tsx`.
3
- * The public TS API in `src/beekon.ts` and `src/types/` is what consumers use;
4
- * mappers in `src/internal/mappers.ts` convert between the two.
2
+ * Codegen TurboModule spec — the single source React Native's Codegen reads to
3
+ * generate the native interface. **DO NOT export Wire* types from
4
+ * `src/index.tsx`.** The public TS API in `src/beekon.ts` and `src/types/` is
5
+ * what consumers use; mappers in `src/internal/mappers.ts` convert between the
6
+ * two.
5
7
  *
6
- * Wire types are flat (primitives + nested flat objects) because Codegen's
7
- * TS-to-Kotlin/ObjC type derivation has limited support for unions and
8
- * recursive structures. State variants are encoded as a `type` discriminator
9
- * string with sibling fields nullable per variant.
8
+ * Wire types are deliberately **flat** (primitives + flat nested objects +
9
+ * arrays) because Codegen's TSKotlin/ObjC derivation has no support for unions
10
+ * or recursive structures:
11
+ *
12
+ * - **Enums travel as plain `string`** (e.g. `accuracyMode`, `quality`); the
13
+ * mappers translate to/from the public string-literal unions with a defensive
14
+ * fallback, so an unknown value never crashes a subscriber.
15
+ * - **Sum types** (`WireState`, `WireSyncStatus`) are a `type` discriminator
16
+ * string plus sibling fields nullable per variant.
17
+ * - **String maps** (`headers`, `extras`) travel as `WireKeyValue[]` pairs —
18
+ * Codegen has no portable dictionary type.
19
+ * - **Timestamps** are epoch-milliseconds; intervals are seconds.
10
20
  */
11
21
  import type { CodegenTypes, TurboModule } from 'react-native';
12
22
  import { TurboModuleRegistry } from 'react-native';
13
23
 
14
- export type WireAndroidNotification = {
15
- channelId: string;
16
- channelName: string;
17
- notificationId: number;
18
- title: string;
19
- text: string;
20
- smallIconResName: string;
24
+ /** A single string map entry — see `headers` / `setExtras`. */
25
+ export type WireKeyValue = {
26
+ key: string;
27
+ value: string;
28
+ };
29
+
30
+ export type WireSyncConfig = {
31
+ url: string;
32
+ headers: WireKeyValue[];
33
+ intervalSeconds: number;
34
+ batchSize: number;
35
+ };
36
+
37
+ /** Android-only foreground-service notification overrides. iOS ignores it. */
38
+ export type WireNotificationConfig = {
39
+ title?: string;
40
+ text?: string;
21
41
  };
22
42
 
23
43
  export type WireConfig = {
24
- /** One of: 'saver' | 'balanced' | 'precision'. */
25
- preset: string;
26
- distanceFilterMeters?: number;
27
- intervalMillis?: number;
28
- androidNotification?: WireAndroidNotification;
29
- licenseKey?: string;
44
+ /** All fields required at the wire level — the TS facade applies defaults. */
45
+ minTimeBetweenLocationsSeconds: number;
46
+ minDistanceBetweenLocationsMeters: number;
47
+ /** One of: 'high' | 'balanced' | 'low'. */
48
+ accuracyMode: string;
49
+ /** One of: 'keepTracking' | 'pause' | 'pauseWithCheckIns'. */
50
+ whenStationary: string;
51
+ stationaryRadiusMeters: number;
52
+ detectActivity: boolean;
53
+ /** Omitted keeps tracking local-only. */
54
+ sync?: WireSyncConfig;
55
+ /** Android-only; the iOS native module ignores it. */
56
+ notification?: WireNotificationConfig;
30
57
  };
31
58
 
32
- export type WirePosition = {
59
+ export type WireLocation = {
60
+ id: string;
33
61
  lat: number;
34
62
  lng: number;
35
- accuracy: number;
36
- speed: number;
37
- bearing: number;
38
- altitude: number;
63
+ timestampMs: number;
64
+ /** `null` when the OS did not report a value. */
65
+ accuracy: number | null;
66
+ speed: number | null;
67
+ bearing: number | null;
68
+ altitude: number | null;
69
+ /** One of: 'ok' | 'lowAccuracy' | 'implausibleSpeed'. */
70
+ quality: string;
71
+ /** One of: 'interval' | 'motion' | 'checkIn' | 'geofence' | 'manual'. */
72
+ trigger: string;
73
+ /** One of: 'moving' | 'stationary' | 'unknown'. */
74
+ motion: string;
75
+ /** Activity enum string, or `null` unless activity detection is enabled. */
76
+ activity: string | null;
77
+ isMock: boolean;
78
+ };
79
+
80
+ export type WireGeofence = {
81
+ id: string;
82
+ lat: number;
83
+ lng: number;
84
+ radiusMeters: number;
85
+ notifyOnEntry: boolean;
86
+ notifyOnExit: boolean;
87
+ };
88
+
89
+ export type WireGeofenceEvent = {
90
+ id: string;
91
+ geofenceId: string;
92
+ /** One of: 'enter' | 'exit'. */
93
+ type: string;
39
94
  timestampMs: number;
40
95
  };
41
96
 
42
97
  export type WireState = {
43
- /** One of: 'idle' | 'starting' | 'tracking' | 'paused' | 'stopped'. */
98
+ /** One of: 'idle' | 'tracking' | 'stopped'. */
44
99
  type: string;
45
100
  /**
46
- * Only populated when `type === 'paused'`. One of:
47
- * 'permissionRevoked' | 'locationDisabled' | 'unknown'.
101
+ * Only populated when `type === 'stopped'`. One of: 'user' |
102
+ * 'permissionDenied' | 'locationServicesDisabled' | 'locationUnavailable' |
103
+ * 'system'.
48
104
  */
49
- pauseReason?: string;
105
+ stopReason?: string;
106
+ };
107
+
108
+ export type WireSyncStatus = {
109
+ /** One of: 'idle' | 'pending' | 'failed'. */
110
+ type: string;
111
+ /** Only populated when `type === 'failed'`. One of: 'auth' | 'rejected'. */
112
+ failure?: string;
50
113
  };
51
114
 
52
115
  export interface Spec extends TurboModule {
53
- initialize(): Promise<void>;
54
- configure(config: WireConfig): Promise<void>;
116
+ /**
117
+ * The config crosses as an untyped object (`ReadableMap` on Android,
118
+ * `NSDictionary` on iOS) and is parsed natively. Typing it as the nested
119
+ * {@link WireConfig} struct would force the iOS ObjC++ layer to walk Codegen's
120
+ * generated C++ structs; the facade still builds a `WireConfig`-shaped value
121
+ * via `configToWire`, so the shape is enforced upstream.
122
+ */
123
+ configure(config: CodegenTypes.UnsafeObject): Promise<void>;
55
124
  start(): Promise<void>;
56
125
  stop(): Promise<void>;
57
- shutdown(): Promise<void>;
58
- history(fromMs: number, toMs: number): Promise<WirePosition[]>;
126
+ resumeIfNeeded(): Promise<void>;
127
+
128
+ getLocations(fromMs: number, toMs: number): Promise<WireLocation[]>;
129
+ /**
130
+ * A negative `beforeMs` deletes all stored locations (the wire encoding of
131
+ * the public `deleteLocations()` no-argument case). A plain `number` is used
132
+ * rather than `number | null` for portable Codegen support across platforms.
133
+ */
134
+ deleteLocations(beforeMs: number): Promise<number>;
135
+ pendingUploadCount(): Promise<number>;
136
+
137
+ sync(): Promise<void>;
138
+ setExtras(entries: WireKeyValue[]): Promise<void>;
139
+
140
+ addGeofences(geofences: WireGeofence[]): Promise<void>;
141
+ removeGeofences(ids: string[]): Promise<void>;
142
+ listGeofences(): Promise<WireGeofence[]>;
59
143
 
60
144
  readonly onState: CodegenTypes.EventEmitter<WireState>;
61
- readonly onPosition: CodegenTypes.EventEmitter<WirePosition>;
145
+ readonly onLocation: CodegenTypes.EventEmitter<WireLocation>;
146
+ readonly onGeofenceEvent: CodegenTypes.EventEmitter<WireGeofenceEvent>;
147
+ readonly onSyncStatus: CodegenTypes.EventEmitter<WireSyncStatus>;
62
148
  }
63
149
 
64
150
  export default TurboModuleRegistry.getEnforcing<Spec>('BeekonRn');
package/src/beekon.ts CHANGED
@@ -1,109 +1,281 @@
1
1
  import NativeBeekon from './NativeBeekonRn';
2
2
  import type { BeekonConfig } from './types/config';
3
+ import type { BeekonGeofence, GeofenceEvent } from './types/geofence';
4
+ import type { Location } from './types/location';
3
5
  import type { BeekonState } from './types/state';
4
- import type { Position } from './types/position';
5
- import { configToWire, wireToPosition, wireToState } from './internal/mappers';
6
+ import type { SyncStatus } from './types/sync';
7
+ import {
8
+ configToWire,
9
+ geofenceToWire,
10
+ recordToEntries,
11
+ rethrowAsBeekonError,
12
+ wireToGeofence,
13
+ wireToGeofenceEvent,
14
+ wireToLocation,
15
+ wireToState,
16
+ wireToSyncStatus,
17
+ } from './internal/mappers';
18
+
19
+ type Listener<T> = (value: T) => void;
20
+
21
+ /**
22
+ * Fans the four native EventEmitters out to multiple JS subscribers and adds
23
+ * replay-1 semantics for `state` / `syncStatus` (RN EventEmitters don't replay,
24
+ * so we cache the latest value and hand it to new subscribers immediately —
25
+ * matching the native StateFlow / AsyncStream contract). `location` /
26
+ * `geofenceEvent` are plain fan-out with no replay.
27
+ *
28
+ * Native subscriptions are opened once, on first use, and kept for the app
29
+ * lifetime — the native module collects its flows continuously regardless, so
30
+ * this only keeps the JS cache warm.
31
+ */
32
+ class EventHub {
33
+ private subscribed = false;
34
+ private readonly stateListeners = new Set<Listener<BeekonState>>();
35
+ private readonly locationListeners = new Set<Listener<Location>>();
36
+ private readonly geofenceListeners = new Set<Listener<GeofenceEvent>>();
37
+ private readonly syncListeners = new Set<Listener<SyncStatus>>();
38
+ private lastState: BeekonState | undefined;
39
+ private lastSyncStatus: SyncStatus | undefined;
40
+
41
+ /** Idempotent: opens the native subscriptions the first time it is called. */
42
+ ensureSubscribed(): void {
43
+ if (this.subscribed) return;
44
+ this.subscribed = true;
45
+ NativeBeekon.onState((w) => {
46
+ const s = wireToState(w);
47
+ this.lastState = s;
48
+ this.stateListeners.forEach((cb) => cb(s));
49
+ });
50
+ NativeBeekon.onLocation((w) => {
51
+ const l = wireToLocation(w);
52
+ this.locationListeners.forEach((cb) => cb(l));
53
+ });
54
+ NativeBeekon.onGeofenceEvent((w) => {
55
+ const g = wireToGeofenceEvent(w);
56
+ this.geofenceListeners.forEach((cb) => cb(g));
57
+ });
58
+ NativeBeekon.onSyncStatus((w) => {
59
+ const s = wireToSyncStatus(w);
60
+ this.lastSyncStatus = s;
61
+ this.syncListeners.forEach((cb) => cb(s));
62
+ });
63
+ }
64
+
65
+ onState(cb: Listener<BeekonState>): () => void {
66
+ this.ensureSubscribed();
67
+ this.stateListeners.add(cb);
68
+ if (this.lastState !== undefined) cb(this.lastState);
69
+ return () => {
70
+ this.stateListeners.delete(cb);
71
+ };
72
+ }
73
+
74
+ onLocation(cb: Listener<Location>): () => void {
75
+ this.ensureSubscribed();
76
+ this.locationListeners.add(cb);
77
+ return () => {
78
+ this.locationListeners.delete(cb);
79
+ };
80
+ }
81
+
82
+ onGeofenceEvent(cb: Listener<GeofenceEvent>): () => void {
83
+ this.ensureSubscribed();
84
+ this.geofenceListeners.add(cb);
85
+ return () => {
86
+ this.geofenceListeners.delete(cb);
87
+ };
88
+ }
89
+
90
+ onSyncStatus(cb: Listener<SyncStatus>): () => void {
91
+ this.ensureSubscribed();
92
+ this.syncListeners.add(cb);
93
+ if (this.lastSyncStatus !== undefined) cb(this.lastSyncStatus);
94
+ return () => {
95
+ this.syncListeners.delete(cb);
96
+ };
97
+ }
98
+ }
6
99
 
7
100
  /**
8
- * Public facade for the Beekon SDK. Mirrors the native APIs:
101
+ * Public facade for the Beekon SDK a 1:1 mirror of the native APIs:
9
102
  *
10
- * - Android: `com.wayq.beekon.Beekon` (object)
11
- * - iOS: `Beekon.shared` (actor)
103
+ * - Android: `in.wayq.beekon.Beekon` (object)
104
+ * - iOS: `BeekonKit.Beekon.shared` (actor)
12
105
  *
13
- * Lifecycle: `init()` once `configure(config)` `start()` ...
14
- * `stop()` (idempotent) or `shutdown()` (final teardown). Subscribe to
15
- * `onState` / `onPosition` for live updates; query `history(from, to)` for
16
- * persisted points.
106
+ * There is **no `initialize()`** the native SDKs auto-initialize. Lifecycle:
107
+ * `configure(config)` (optional; defaults 30s / 100m) `start()`
108
+ * `stop()`. `start()` / `stop()` **never throw** `onState` is the single
109
+ * source of truth for whether tracking is active and why it stopped.
17
110
  *
18
- * **Threading:** Methods are safe to call from any JS context. Subscribers'
111
+ * **Threading:** methods are safe to call from any JS context; subscriber
19
112
  * callbacks fire on the JS thread.
20
113
  *
21
- * **Persistence invariant:** the SDK persists every gated position natively;
22
- * JS is a passive observer. In background, the JS engine is not guaranteed to
23
- * be alive — for past points, use `history()`.
114
+ * **Persistence invariant:** the SDK persists every gated fix natively; JS is a
115
+ * passive observer that is not guaranteed to be alive in the background. For
116
+ * past fixes use `getLocations()`.
24
117
  */
25
118
  class BeekonImpl {
119
+ private readonly hub = new EventHub();
120
+
26
121
  /**
27
- * Initialize the SDK. Idempotent; safe to call more than once. Must be
28
- * called once before `configure()`.
29
- *
30
- * Errors:
31
- * - `NO_GMS_AVAILABLE` (Android) — Google Play Services missing.
122
+ * Set tracking parameters. Optional `start()` falls back to the previously
123
+ * persisted config, or the SDK defaults if never configured. While tracking,
124
+ * gate thresholds take effect on the next admitted fix without restarting the
125
+ * location subscription; `accuracyMode` applies on the next `start()`.
32
126
  */
33
- async init(): Promise<void> {
34
- return NativeBeekon.initialize();
127
+ async configure(config: BeekonConfig): Promise<void> {
128
+ this.hub.ensureSubscribed();
129
+ await NativeBeekon.configure(configToWire(config));
35
130
  }
36
131
 
37
132
  /**
38
- * Set sampling and platform-specific config. Replaces any previous config.
39
- * Must be called before `start()`.
133
+ * Begin tracking. Never throws observe `onState` for the outcome
134
+ * (`stopped(reason)` on a permission / services / system problem). Beekon
135
+ * ships no permission API: request permissions in the host app, then observe
136
+ * `onState`.
137
+ */
138
+ async start(): Promise<void> {
139
+ this.hub.ensureSubscribed();
140
+ await NativeBeekon.start();
141
+ }
142
+
143
+ /** Stop tracking. Idempotent. Registered geofences keep firing. */
144
+ async stop(): Promise<void> {
145
+ this.hub.ensureSubscribed();
146
+ await NativeBeekon.stop();
147
+ }
148
+
149
+ /**
150
+ * Resume tracking if a session was active before the app was terminated. A
151
+ * no-op if the user explicitly stopped. Call early in app startup.
40
152
  *
41
- * Errors:
42
- * - `NOT_INITIALISED` `init()` was not called first.
43
- * - `NOT_CONFIGURED` Android only, `androidNotification` was missing.
153
+ * Note: on a background relaunch the JS engine may not be running, so true
154
+ * cold-launch resume also requires native host wiring (Android: call
155
+ * `Beekon.start()` from `Application.onCreate`; iOS: `registerBackgroundTasks`
156
+ * in the AppDelegate). See the README.
44
157
  */
45
- async configure(config: BeekonConfig): Promise<void> {
46
- return NativeBeekon.configure(configToWire(config));
158
+ async resumeIfNeeded(): Promise<void> {
159
+ this.hub.ensureSubscribed();
160
+ await NativeBeekon.resumeIfNeeded();
47
161
  }
48
162
 
49
163
  /**
50
- * Begin tracking. Transitions state from `idle` `starting` `tracking`.
164
+ * Read persisted fixes in the inclusive range `[from, to]`, oldest first.
165
+ * Reads come from the SDK's local storage (Room on Android, GRDB on iOS) — the
166
+ * source of truth even when JS was asleep. With sync enabled, rows are deleted
167
+ * locally once the server accepts them, so this returns only un-uploaded fixes.
51
168
  *
52
- * Errors:
53
- * - `PERMISSION_DENIED` — location permission not granted.
54
- * - `LOCATION_SERVICES_DISABLED` (iOS) — system location services off.
55
- * - `SERVICE_FAILED` — native service couldn't start.
169
+ * Throws `BeekonError` with kind `'storage'` on a database read failure.
56
170
  */
57
- async start(): Promise<void> {
58
- return NativeBeekon.start();
171
+ async getLocations(from: Date, to: Date): Promise<Location[]> {
172
+ try {
173
+ const wires = await NativeBeekon.getLocations(
174
+ from.getTime(),
175
+ to.getTime()
176
+ );
177
+ return wires.map(wireToLocation);
178
+ } catch (e) {
179
+ rethrowAsBeekonError(e);
180
+ }
59
181
  }
60
182
 
61
- /** Stop tracking. Idempotent. State → `stopped`. */
62
- async stop(): Promise<void> {
63
- return NativeBeekon.stop();
183
+ /**
184
+ * Delete stored locations captured at or before `before` (all of them when
185
+ * `before` is omitted). Returns the number of rows removed. Throws
186
+ * `BeekonError` with kind `'storage'` on a failure.
187
+ */
188
+ async deleteLocations(before?: Date): Promise<number> {
189
+ try {
190
+ // Negative is the wire sentinel for "delete all" (see NativeBeekonRn.ts).
191
+ return await NativeBeekon.deleteLocations(before ? before.getTime() : -1);
192
+ } catch (e) {
193
+ rethrowAsBeekonError(e);
194
+ }
64
195
  }
65
196
 
66
197
  /**
67
- * Final teardown releases native resources. Most apps don't need this;
68
- * `stop()` is sufficient between sessions.
198
+ * The number of stored locations not yet uploaded. Throws `BeekonError` with
199
+ * kind `'storage'` on a failure.
69
200
  */
70
- async shutdown(): Promise<void> {
71
- return NativeBeekon.shutdown();
201
+ async pendingUploadCount(): Promise<number> {
202
+ try {
203
+ return await NativeBeekon.pendingUploadCount();
204
+ } catch (e) {
205
+ rethrowAsBeekonError(e);
206
+ }
72
207
  }
73
208
 
74
209
  /**
75
- * Read persisted positions in the time range [from, to]. Returns positions
76
- * in chronological order. Reads come from the SDK's local storage (Room on
77
- * Android, GRDB on iOS) — the source of truth even when JS was asleep.
78
- *
79
- * Retention: positions older than 7 days OR beyond the most recent 100K are
80
- * pruned automatically.
210
+ * Request an immediate upload. Best-effort and self-retrying; a no-op when
211
+ * sync is not configured.
81
212
  */
82
- async history(from: Date, to: Date): Promise<Position[]> {
83
- const wires = await NativeBeekon.history(from.getTime(), to.getTime());
84
- return wires.map(wireToPosition);
213
+ async sync(): Promise<void> {
214
+ await NativeBeekon.sync();
215
+ }
216
+
217
+ /** Set custom key/value fields (e.g. `user_id`) included with every upload. */
218
+ async setExtras(extras: Record<string, string>): Promise<void> {
219
+ await NativeBeekon.setExtras(recordToEntries(extras));
85
220
  }
86
221
 
87
222
  /**
88
- * Subscribe to state transitions. Returns an unsubscribe function. The
89
- * current state is delivered to new subscribers immediately (replay-1
90
- * semantics, matching the native `state` flow).
223
+ * Register geofences. Re-adding an `id` replaces the existing one. Throws
224
+ * `BeekonError` with kind `'invalidGeofence'` if any entry fails validation —
225
+ * none are added when one is invalid.
226
+ */
227
+ async addGeofences(geofences: BeekonGeofence[]): Promise<void> {
228
+ try {
229
+ await NativeBeekon.addGeofences(geofences.map(geofenceToWire));
230
+ } catch (e) {
231
+ rethrowAsBeekonError(e);
232
+ }
233
+ }
234
+
235
+ /** Unregister geofences by id. Unknown ids are ignored. */
236
+ async removeGeofences(ids: string[]): Promise<void> {
237
+ await NativeBeekon.removeGeofences(ids);
238
+ }
239
+
240
+ /** The currently registered geofences. */
241
+ async listGeofences(): Promise<BeekonGeofence[]> {
242
+ const wires = await NativeBeekon.listGeofences();
243
+ return wires.map(wireToGeofence);
244
+ }
245
+
246
+ /**
247
+ * Subscribe to tracking-state transitions (`idle` / `tracking` /
248
+ * `stopped(reason)`). The current state is delivered to new subscribers
249
+ * immediately (replay-1). Returns an unsubscribe function.
91
250
  */
92
251
  onState(cb: (s: BeekonState) => void): () => void {
93
- const subscription = NativeBeekon.onState((wire) => cb(wireToState(wire)));
94
- return () => subscription.remove();
252
+ return this.hub.onState(cb);
253
+ }
254
+
255
+ /**
256
+ * Subscribe to gated locations as they arrive. Broadcast (no replay) — only
257
+ * delivers while the JS engine is alive. For fixes emitted while JS was
258
+ * asleep, use `getLocations()`. Returns an unsubscribe function.
259
+ */
260
+ onLocation(cb: (l: Location) => void): () => void {
261
+ return this.hub.onLocation(cb);
262
+ }
263
+
264
+ /**
265
+ * Subscribe to geofence enter / exit crossings. Broadcast (no replay).
266
+ * Returns an unsubscribe function.
267
+ */
268
+ onGeofenceEvent(cb: (e: GeofenceEvent) => void): () => void {
269
+ return this.hub.onGeofenceEvent(cb);
95
270
  }
96
271
 
97
272
  /**
98
- * Subscribe to gated positions as they arrive. Returns an unsubscribe
99
- * function. Broadcast (no replay) only delivers while the JS engine is
100
- * alive. For points emitted while JS was asleep, use `history()`.
273
+ * Subscribe to upload health (`idle` / `pending` / `failed(reason)`). The
274
+ * current status is delivered to new subscribers immediately (replay-1).
275
+ * Returns an unsubscribe function.
101
276
  */
102
- onPosition(cb: (p: Position) => void): () => void {
103
- const subscription = NativeBeekon.onPosition((wire) =>
104
- cb(wireToPosition(wire))
105
- );
106
- return () => subscription.remove();
277
+ onSyncStatus(cb: (s: SyncStatus) => void): () => void {
278
+ return this.hub.onSyncStatus(cb);
107
279
  }
108
280
  }
109
281
 
package/src/index.tsx CHANGED
@@ -1,5 +1,24 @@
1
1
  export { Beekon } from './beekon';
2
- export type { BeekonConfig, AndroidNotificationConfig } from './types/config';
3
- export type { Preset } from './types/preset';
4
- export type { BeekonState, PauseReason } from './types/state';
5
- export type { Position } from './types/position';
2
+
3
+ export type {
4
+ BeekonConfig,
5
+ SyncConfig,
6
+ NotificationConfig,
7
+ } from './types/config';
8
+ export type {
9
+ AccuracyMode,
10
+ StationaryMode,
11
+ LocationTrigger,
12
+ LocationQuality,
13
+ MotionState,
14
+ ActivityType,
15
+ } from './types/enums';
16
+ export type { Location } from './types/location';
17
+ export type {
18
+ BeekonGeofence,
19
+ GeofenceEvent,
20
+ Transition,
21
+ } from './types/geofence';
22
+ export type { BeekonState, StopReason } from './types/state';
23
+ export type { SyncStatus, SyncFailure } from './types/sync';
24
+ export { BeekonError, type BeekonErrorKind } from './types/error';