@wayq/beekon-rn 0.0.1

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 (69) hide show
  1. package/BeekonRn.podspec +37 -0
  2. package/LICENSE.txt +14 -0
  3. package/README.md +122 -0
  4. package/android/build.gradle +81 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/wayq/beekonrn/BeekonRnModule.kt +233 -0
  7. package/android/src/main/java/com/wayq/beekonrn/BeekonRnPackage.kt +31 -0
  8. package/ios/BeekonRn.h +5 -0
  9. package/ios/BeekonRn.mm +80 -0
  10. package/ios/BeekonRn.swift +211 -0
  11. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +44 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/LICENSE.txt +14 -0
  13. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeDirectory +0 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeResources +233 -0
  17. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeSignature +0 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/PrivacyInfo.xcprivacy +77 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/_CodeSignature/CodeResources +113 -0
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  23. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  24. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/PrivacyInfo.xcprivacy +77 -0
  25. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +113 -0
  26. package/lib/module/NativeBeekonRn.js +16 -0
  27. package/lib/module/NativeBeekonRn.js.map +1 -0
  28. package/lib/module/beekon.js +107 -0
  29. package/lib/module/beekon.js.map +1 -0
  30. package/lib/module/index.js +4 -0
  31. package/lib/module/index.js.map +1 -0
  32. package/lib/module/internal/mappers.js +58 -0
  33. package/lib/module/internal/mappers.js.map +1 -0
  34. package/lib/module/package.json +1 -0
  35. package/lib/module/types/config.js +4 -0
  36. package/lib/module/types/config.js.map +1 -0
  37. package/lib/module/types/position.js +2 -0
  38. package/lib/module/types/position.js.map +1 -0
  39. package/lib/module/types/preset.js +2 -0
  40. package/lib/module/types/preset.js.map +1 -0
  41. package/lib/module/types/state.js +2 -0
  42. package/lib/module/types/state.js.map +1 -0
  43. package/lib/typescript/package.json +1 -0
  44. package/lib/typescript/src/NativeBeekonRn.d.ts +58 -0
  45. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -0
  46. package/lib/typescript/src/beekon.d.ts +80 -0
  47. package/lib/typescript/src/beekon.d.ts.map +1 -0
  48. package/lib/typescript/src/index.d.ts +6 -0
  49. package/lib/typescript/src/index.d.ts.map +1 -0
  50. package/lib/typescript/src/internal/mappers.d.ts +8 -0
  51. package/lib/typescript/src/internal/mappers.d.ts.map +1 -0
  52. package/lib/typescript/src/types/config.d.ts +41 -0
  53. package/lib/typescript/src/types/config.d.ts.map +1 -0
  54. package/lib/typescript/src/types/position.d.ts +24 -0
  55. package/lib/typescript/src/types/position.d.ts.map +1 -0
  56. package/lib/typescript/src/types/preset.d.ts +12 -0
  57. package/lib/typescript/src/types/preset.d.ts.map +1 -0
  58. package/lib/typescript/src/types/state.d.ts +22 -0
  59. package/lib/typescript/src/types/state.d.ts.map +1 -0
  60. package/package.json +137 -0
  61. package/scripts/fetch-beekonkit.sh +58 -0
  62. package/src/NativeBeekonRn.ts +64 -0
  63. package/src/beekon.ts +110 -0
  64. package/src/index.tsx +5 -0
  65. package/src/internal/mappers.ts +50 -0
  66. package/src/types/config.ts +42 -0
  67. package/src/types/position.ts +23 -0
  68. package/src/types/preset.ts +11 -0
  69. package/src/types/state.ts +16 -0
@@ -0,0 +1,37 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "BeekonRn"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ # Match RN 0.85 default Podfile floor (15.1). Native BeekonKit's floor is
14
+ # 15.0 so this is satisfied. Bump only if RN raises its default.
15
+ s.platforms = { :ios => "15.1" }
16
+ s.source = { :git => "https://github.com/wayqteam/beekon.git", :tag => "v#{s.version}" }
17
+
18
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
19
+ # Headers are only for the auto-generated `BeekonRn-Swift.h` interop — keep
20
+ # them private so they don't leak into the consuming app's umbrella.
21
+ s.private_header_files = "ios/**/*.h"
22
+
23
+ # Native Beekon SDK as a vendored xcframework. The zip is fetched from
24
+ # `wayqteam/beekon-ios-binary`'s GitHub Release at this version into
25
+ # `ios/Frameworks/` by `scripts/fetch-beekonkit.sh`, run via `yarn prepare`,
26
+ # and bundled in the npm tarball at publish time. SHA256 verified before
27
+ # extraction.
28
+ s.vendored_frameworks = "ios/Frameworks/BeekonKit.xcframework"
29
+
30
+ # Swift 5.10 covers BeekonKit's binary and our wrapper. Don't pin higher
31
+ # than necessary — consumer apps may use newer Xcode.
32
+ s.swift_versions = ["5.10", "6.0"]
33
+
34
+ # `install_modules_dependencies` wires React-Codegen, RCT-Folly, and the
35
+ # rest of the New Architecture deps. Must be last so it can override.
36
+ install_modules_dependencies(s)
37
+ end
package/LICENSE.txt ADDED
@@ -0,0 +1,14 @@
1
+ Beekon SDK
2
+ Copyright (c) 2026 wayqteam. All rights reserved.
3
+
4
+ This software is licensed for evaluation use only. No production deployment,
5
+ redistribution, sublicensing, or commercial use is permitted without a separate
6
+ written agreement with wayqteam.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
10
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
11
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
12
+ FROM USE OF THE SOFTWARE.
13
+
14
+ For licensing inquiries: contact wayqteam.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @wayq/beekon-rn
2
+
3
+ React Native binding for the [Beekon](https://github.com/wayqteam/beekon) location SDK.
4
+
5
+ A thin pass-through over the native Android (`io.github.wayqteam:beekon`) and iOS (`BeekonKit`) libraries. The public TypeScript surface mirrors the Kotlin / Swift / Dart APIs in shape: `init / configure / start / stop`, callback-backed `state` and `positions` streams, and a `history(from, to)` query.
6
+
7
+ Built on the React Native New Architecture (TurboModules + Codegen). The binding is a thin delegation layer — all logic, persistence, and OS integration lives natively. **Writes never cross into JavaScript**: in background the JS engine isn't guaranteed to be alive, so the native libraries own the persistence path end-to-end (same rule as the Flutter binding).
8
+
9
+ See [`../docs/REQUIREMENTS.md`](../docs/REQUIREMENTS.md) for the full cross-platform spec.
10
+
11
+ ## Requirements
12
+
13
+ | Area | Floor |
14
+ |---|---|
15
+ | React Native | 0.76+ (target 0.85) |
16
+ | iOS | 15.1 |
17
+ | Android | API 24 |
18
+ | New Architecture | required (Old Arch is unsupported) |
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm install @wayq/beekon-rn
24
+ # or
25
+ yarn add @wayq/beekon-rn
26
+ ```
27
+
28
+ iOS:
29
+
30
+ ```sh
31
+ cd ios && pod install
32
+ ```
33
+
34
+ The package bundles `BeekonKit.xcframework` directly — no manual SwiftPM setup needed.
35
+
36
+ Android: Maven Central is auto-included by the autolinker. The native AAR (`io.github.wayqteam:beekon`) is pulled transitively.
37
+
38
+ ## Permissions
39
+
40
+ The library does NOT request permissions itself — your app must. Use a library like [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) or platform APIs.
41
+
42
+ **Android** (`AndroidManifest.xml`):
43
+
44
+ ```xml
45
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
46
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
47
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
48
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
49
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
50
+ ```
51
+
52
+ **iOS** (`Info.plist`):
53
+
54
+ ```xml
55
+ <key>NSLocationWhenInUseUsageDescription</key>
56
+ <string>...</string>
57
+ <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
58
+ <string>...</string>
59
+ <key>UIBackgroundModes</key>
60
+ <array><string>location</string></array>
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ ```ts
66
+ import { Beekon, type BeekonState, type Position } from '@wayq/beekon-rn';
67
+
68
+ // Subscribe — returns an unsubscribe function.
69
+ const offState = Beekon.onState((s: BeekonState) => console.log('state', s));
70
+ const offPos = Beekon.onPosition((p: Position) => console.log('pos', p));
71
+
72
+ // Initialize once at startup.
73
+ await Beekon.init();
74
+
75
+ // Configure (Android requires `androidNotification`; iOS ignores it).
76
+ await Beekon.configure({
77
+ preset: 'balanced', // or 'saver' / 'precision'
78
+ androidNotification: {
79
+ channelId: 'beekon_tracking',
80
+ channelName: 'Beekon location tracking',
81
+ notificationId: 1001,
82
+ title: 'Tracking',
83
+ text: 'Recording your route',
84
+ smallIconResName: 'ic_notification', // drawable in your app
85
+ },
86
+ });
87
+
88
+ await Beekon.start();
89
+ // ... later
90
+ await Beekon.stop();
91
+
92
+ // Read past positions.
93
+ const pts = await Beekon.history(new Date(Date.now() - 3600_000), new Date());
94
+
95
+ // Cleanup.
96
+ offState();
97
+ offPos();
98
+ ```
99
+
100
+ ## API
101
+
102
+ | Method | Description |
103
+ |---|---|
104
+ | `Beekon.init()` | One-time initialization. Idempotent. |
105
+ | `Beekon.configure(config)` | Set sampling / Android notification config. |
106
+ | `Beekon.start()` | Begin tracking. State → `starting` → `tracking`. |
107
+ | `Beekon.stop()` | Stop tracking. Idempotent. State → `stopped`. |
108
+ | `Beekon.shutdown()` | Final teardown. Most apps don't need this. |
109
+ | `Beekon.history(from, to)` | Read persisted positions in range. |
110
+ | `Beekon.onState(cb)` | Subscribe to state. Returns unsubscribe fn. |
111
+ | `Beekon.onPosition(cb)` | Subscribe to gated positions. Returns unsubscribe fn. |
112
+
113
+ Errors are thrown as `Error` instances with stable platform-agnostic codes:
114
+ `NOT_INITIALISED`, `NOT_CONFIGURED`, `PERMISSION_DENIED`, `LOCATION_SERVICES_DISABLED` (iOS), `NO_GMS_AVAILABLE` (Android), `SERVICE_FAILED`, `INTERNAL_ERROR`.
115
+
116
+ ## Storage and retention
117
+
118
+ The native SDKs persist every gated position locally (Room on Android, GRDB on iOS) — JS is a passive reader. Retention: **TTL 7 days OR most recent 100 K rows**, whichever is smaller; auto-pruned on each write batch.
119
+
120
+ ## License
121
+
122
+ Proprietary — see [LICENSE.txt](LICENSE.txt). Evaluation use only without a separate written agreement with wayqteam.
@@ -0,0 +1,81 @@
1
+ buildscript {
2
+ ext.BeekonRn = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return BeekonRn[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.wayq.beekonrn"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
43
+ }
44
+
45
+ buildFeatures {
46
+ buildConfig true
47
+ }
48
+
49
+ buildTypes {
50
+ release {
51
+ minifyEnabled false
52
+ }
53
+ }
54
+
55
+ lint {
56
+ disable "GradleCompatible"
57
+ }
58
+
59
+ compileOptions {
60
+ sourceCompatibility JavaVersion.VERSION_1_8
61
+ targetCompatibility JavaVersion.VERSION_1_8
62
+ }
63
+ }
64
+
65
+ repositories {
66
+ google()
67
+ mavenCentral()
68
+ }
69
+
70
+ dependencies {
71
+ implementation "com.facebook.react:react-android"
72
+ // Native Beekon SDK — published from beekon-android/ via Maven Central.
73
+ // Pinned exact in v0.x to avoid surprise breakage; loosen to a range when
74
+ // the SDK reaches v1 stability.
75
+ implementation "io.github.wayqteam:beekon:0.0.1"
76
+ // Kotlin coroutines — required for collecting Beekon's StateFlow/SharedFlow.
77
+ // Beekon already depends on coroutines transitively, but declaring it here
78
+ // makes the dependency intent explicit.
79
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
80
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
81
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,233 @@
1
+ package com.wayq.beekonrn
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.WritableArray
8
+ import com.facebook.react.bridge.WritableMap
9
+ import com.wayq.beekon.Beekon
10
+ import com.wayq.beekon.BeekonConfig
11
+ import com.wayq.beekon.BeekonError
12
+ import com.wayq.beekon.BeekonState
13
+ import com.wayq.beekon.NotificationConfig
14
+ import com.wayq.beekon.PauseReason
15
+ import com.wayq.beekon.Position
16
+ import com.wayq.beekon.Preset
17
+ import java.time.Instant
18
+ import kotlinx.coroutines.CoroutineScope
19
+ import kotlinx.coroutines.Dispatchers
20
+ import kotlinx.coroutines.Job
21
+ import kotlinx.coroutines.SupervisorJob
22
+ import kotlinx.coroutines.cancel
23
+ import kotlinx.coroutines.flow.collect
24
+ import kotlinx.coroutines.launch
25
+
26
+ class BeekonRnModule(private val reactContext: ReactApplicationContext) :
27
+ NativeBeekonRnSpec(reactContext) {
28
+
29
+ // Default dispatcher (not Main.immediate) — Codegen's emitOnX is thread-safe
30
+ // and marshals to JS internally, so collecting on Main buys nothing and
31
+ // risks jank if any mapper does work. SupervisorJob so a single failure
32
+ // doesn't tear down siblings.
33
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
34
+ private var stateJob: Job? = null
35
+ private var positionsJob: Job? = null
36
+
37
+ override fun initialize(promise: Promise) {
38
+ scope.launch {
39
+ try {
40
+ Beekon.initialize(reactContext.applicationContext)
41
+
42
+ // Idempotent: re-init replaces collectors. Native flows are hot
43
+ // (StateFlow / SharedFlow) so resubscription replays the latest value.
44
+ stateJob?.cancel()
45
+ positionsJob?.cancel()
46
+ stateJob = scope.launch {
47
+ Beekon.state.collect { s -> emitOnState(stateToWire(s)) }
48
+ }
49
+ positionsJob = scope.launch {
50
+ Beekon.positions.collect { p -> emitOnPosition(positionToWire(p)) }
51
+ }
52
+ promise.resolve(null)
53
+ } catch (t: Throwable) {
54
+ promise.reject(errorCode(t), t.message ?: "init failed", t)
55
+ }
56
+ }
57
+ }
58
+
59
+ override fun configure(config: ReadableMap, promise: Promise) {
60
+ scope.launch {
61
+ try {
62
+ Beekon.configure(wireToConfig(config))
63
+ promise.resolve(null)
64
+ } catch (t: Throwable) {
65
+ promise.reject(errorCode(t), t.message ?: "configure failed", t)
66
+ }
67
+ }
68
+ }
69
+
70
+ override fun start(promise: Promise) {
71
+ scope.launch {
72
+ try {
73
+ Beekon.start()
74
+ promise.resolve(null)
75
+ } catch (t: Throwable) {
76
+ promise.reject(errorCode(t), t.message ?: "start failed", t)
77
+ }
78
+ }
79
+ }
80
+
81
+ override fun stop(promise: Promise) {
82
+ scope.launch {
83
+ try {
84
+ Beekon.stop()
85
+ promise.resolve(null)
86
+ } catch (t: Throwable) {
87
+ promise.reject(errorCode(t), t.message ?: "stop failed", t)
88
+ }
89
+ }
90
+ }
91
+
92
+ override fun shutdown(promise: Promise) {
93
+ scope.launch {
94
+ try {
95
+ stateJob?.cancel()
96
+ positionsJob?.cancel()
97
+ Beekon.shutdown()
98
+ promise.resolve(null)
99
+ } catch (t: Throwable) {
100
+ promise.reject(errorCode(t), t.message ?: "shutdown failed", t)
101
+ }
102
+ }
103
+ }
104
+
105
+ override fun history(fromMs: Double, toMs: Double, promise: Promise) {
106
+ scope.launch {
107
+ try {
108
+ val from = Instant.ofEpochMilli(fromMs.toLong())
109
+ val to = Instant.ofEpochMilli(toMs.toLong())
110
+ val positions = Beekon.history(from, to)
111
+ val arr: WritableArray = Arguments.createArray()
112
+ for (p in positions) arr.pushMap(positionToWire(p))
113
+ promise.resolve(arr)
114
+ } catch (t: Throwable) {
115
+ promise.reject(errorCode(t), t.message ?: "history failed", t)
116
+ }
117
+ }
118
+ }
119
+
120
+ override fun invalidate() {
121
+ super.invalidate()
122
+ scope.cancel()
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Mappers (Wire ↔ Kotlin types)
127
+ // ---------------------------------------------------------------------------
128
+
129
+ private fun wireToConfig(map: ReadableMap): BeekonConfig {
130
+ val preset = when (map.getString("preset")) {
131
+ "saver" -> Preset.Saver
132
+ "precision" -> Preset.Precision
133
+ else -> Preset.Balanced
134
+ }
135
+ val distanceFilterMeters =
136
+ if (map.hasKey("distanceFilterMeters") && !map.isNull("distanceFilterMeters"))
137
+ map.getDouble("distanceFilterMeters").toFloat()
138
+ else null
139
+ val intervalMillis =
140
+ if (map.hasKey("intervalMillis") && !map.isNull("intervalMillis"))
141
+ map.getDouble("intervalMillis").toLong()
142
+ else null
143
+ val notifMap = map.getMap("androidNotification")
144
+ ?: throw IllegalArgumentException(
145
+ "androidNotification is required when running on Android"
146
+ )
147
+ val licenseKey =
148
+ if (map.hasKey("licenseKey") && !map.isNull("licenseKey"))
149
+ map.getString("licenseKey")
150
+ else null
151
+
152
+ return BeekonConfig(
153
+ preset = preset,
154
+ distanceFilterMeters = distanceFilterMeters,
155
+ intervalMillis = intervalMillis,
156
+ notification = wireToNotificationConfig(notifMap),
157
+ licenseKey = licenseKey,
158
+ )
159
+ }
160
+
161
+ private fun wireToNotificationConfig(map: ReadableMap): NotificationConfig {
162
+ val resName = map.getString("smallIconResName")
163
+ ?: throw IllegalArgumentException("smallIconResName is required")
164
+ val pkg = reactContext.packageName
165
+ val iconId = reactContext.resources.getIdentifier(resName, "drawable", pkg)
166
+ if (iconId == 0) {
167
+ throw IllegalArgumentException(
168
+ "drawable resource '$resName' not found in package '$pkg'"
169
+ )
170
+ }
171
+ return NotificationConfig(
172
+ channelId = map.getString("channelId")
173
+ ?: throw IllegalArgumentException("channelId is required"),
174
+ channelName = map.getString("channelName")
175
+ ?: throw IllegalArgumentException("channelName is required"),
176
+ notificationId = map.getInt("notificationId"),
177
+ title = map.getString("title")
178
+ ?: throw IllegalArgumentException("title is required"),
179
+ text = map.getString("text")
180
+ ?: throw IllegalArgumentException("text is required"),
181
+ smallIcon = iconId,
182
+ )
183
+ }
184
+
185
+ private fun positionToWire(p: Position): WritableMap {
186
+ val m = Arguments.createMap()
187
+ m.putDouble("lat", p.lat)
188
+ m.putDouble("lng", p.lng)
189
+ m.putDouble("accuracy", p.accuracy.toDouble())
190
+ m.putDouble("speed", p.speed.toDouble())
191
+ m.putDouble("bearing", p.bearing.toDouble())
192
+ m.putDouble("altitude", p.altitude)
193
+ m.putDouble("timestampMs", p.timestamp.toEpochMilli().toDouble())
194
+ return m
195
+ }
196
+
197
+ private fun stateToWire(s: BeekonState): WritableMap {
198
+ val m = Arguments.createMap()
199
+ when (s) {
200
+ BeekonState.Idle -> m.putString("type", "idle")
201
+ BeekonState.Starting -> m.putString("type", "starting")
202
+ BeekonState.Tracking -> m.putString("type", "tracking")
203
+ is BeekonState.Paused -> {
204
+ m.putString("type", "paused")
205
+ m.putString("pauseReason", pauseReasonToWire(s.reason))
206
+ }
207
+ BeekonState.Stopped -> m.putString("type", "stopped")
208
+ }
209
+ return m
210
+ }
211
+
212
+ private fun pauseReasonToWire(r: PauseReason): String = when (r) {
213
+ PauseReason.PermissionRevoked -> "permissionRevoked"
214
+ PauseReason.LocationDisabled -> "locationDisabled"
215
+ PauseReason.Unknown -> "unknown"
216
+ }
217
+
218
+ // Maps native error sealed types to stable JS-side codes. Same code strings
219
+ // on iOS so JS can switch on them platform-agnostically.
220
+ private fun errorCode(t: Throwable): String = when (t) {
221
+ is BeekonError.NotInitialised -> "NOT_INITIALISED"
222
+ is BeekonError.NotConfigured -> "NOT_CONFIGURED"
223
+ is BeekonError.PermissionDenied -> "PERMISSION_DENIED"
224
+ is BeekonError.NoGmsAvailable -> "NO_GMS_AVAILABLE"
225
+ is BeekonError.ServiceFailed -> "SERVICE_FAILED"
226
+ is BeekonError.InternalError -> "INTERNAL_ERROR"
227
+ else -> "INTERNAL_ERROR"
228
+ }
229
+
230
+ companion object {
231
+ const val NAME = NativeBeekonRnSpec.NAME
232
+ }
233
+ }
@@ -0,0 +1,31 @@
1
+ package com.wayq.beekonrn
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class BeekonRnPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == BeekonRnModule.NAME) {
13
+ BeekonRnModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
20
+ mapOf(
21
+ BeekonRnModule.NAME to ReactModuleInfo(
22
+ name = BeekonRnModule.NAME,
23
+ className = BeekonRnModule.NAME,
24
+ canOverrideExistingModule = false,
25
+ needsEagerInit = false,
26
+ isCxxModule = false,
27
+ isTurboModule = true
28
+ )
29
+ )
30
+ }
31
+ }
package/ios/BeekonRn.h ADDED
@@ -0,0 +1,5 @@
1
+ #import <BeekonRnSpec/BeekonRnSpec.h>
2
+
3
+ @interface BeekonRn : NSObject <NativeBeekonRnSpec>
4
+
5
+ @end
@@ -0,0 +1,80 @@
1
+ #import "BeekonRn.h"
2
+ // Auto-generated bridging header from the Swift impl in this same target.
3
+ #import "BeekonRn-Swift.h"
4
+
5
+ @implementation BeekonRn {
6
+ BeekonRnImpl *_impl;
7
+ }
8
+
9
+ - (instancetype)init {
10
+ self = [super init];
11
+ if (self) {
12
+ __weak __typeof(self) weakSelf = self;
13
+ _impl = [[BeekonRnImpl alloc]
14
+ initWithOnState:^(NSDictionary *_Nonnull s) {
15
+ [weakSelf emitOnState:s];
16
+ }
17
+ onPosition:^(NSDictionary *_Nonnull p) {
18
+ [weakSelf emitOnPosition:p];
19
+ }];
20
+ }
21
+ return self;
22
+ }
23
+
24
+ - (void)initialize:(RCTPromiseResolveBlock)resolve
25
+ reject:(RCTPromiseRejectBlock)reject {
26
+ [_impl initializeWithResolver:resolve rejecter:reject];
27
+ }
28
+
29
+ - (void)configure:(JS::NativeBeekonRn::WireConfig &)config
30
+ resolve:(RCTPromiseResolveBlock)resolve
31
+ reject:(RCTPromiseRejectBlock)reject {
32
+ // Codegen passes the struct; round-trip through NSDictionary so the Swift
33
+ // side can decode generically without an iOS-specific spec import.
34
+ // (`config.toDictionary()` may also be available depending on RN version.)
35
+ NSDictionary *dict = @{
36
+ @"preset": config.preset() ?: @"balanced",
37
+ @"distanceFilterMeters": config.distanceFilterMeters().has_value()
38
+ ? @(config.distanceFilterMeters().value())
39
+ : (id)[NSNull null],
40
+ @"intervalMillis": config.intervalMillis().has_value()
41
+ ? @(config.intervalMillis().value())
42
+ : (id)[NSNull null],
43
+ @"licenseKey": config.licenseKey() ?: (id)[NSNull null],
44
+ };
45
+ // Note: iOS doesn't use androidNotification — passed but ignored.
46
+ [_impl configure:dict resolver:resolve rejecter:reject];
47
+ }
48
+
49
+ - (void)start:(RCTPromiseResolveBlock)resolve
50
+ reject:(RCTPromiseRejectBlock)reject {
51
+ [_impl startWithResolver:resolve rejecter:reject];
52
+ }
53
+
54
+ - (void)stop:(RCTPromiseResolveBlock)resolve
55
+ reject:(RCTPromiseRejectBlock)reject {
56
+ [_impl stopWithResolver:resolve rejecter:reject];
57
+ }
58
+
59
+ - (void)shutdown:(RCTPromiseResolveBlock)resolve
60
+ reject:(RCTPromiseRejectBlock)reject {
61
+ [_impl shutdownWithResolver:resolve rejecter:reject];
62
+ }
63
+
64
+ - (void)history:(double)fromMs
65
+ toMs:(double)toMs
66
+ resolve:(RCTPromiseResolveBlock)resolve
67
+ reject:(RCTPromiseRejectBlock)reject {
68
+ [_impl historyFromMs:fromMs toMs:toMs resolver:resolve rejecter:reject];
69
+ }
70
+
71
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
72
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
73
+ return std::make_shared<facebook::react::NativeBeekonRnSpecJSI>(params);
74
+ }
75
+
76
+ + (NSString *)moduleName {
77
+ return @"BeekonRn";
78
+ }
79
+
80
+ @end