@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.
- package/BeekonRn.podspec +37 -0
- package/LICENSE.txt +14 -0
- package/README.md +122 -0
- package/android/build.gradle +81 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/wayq/beekonrn/BeekonRnModule.kt +233 -0
- package/android/src/main/java/com/wayq/beekonrn/BeekonRnPackage.kt +31 -0
- package/ios/BeekonRn.h +5 -0
- package/ios/BeekonRn.mm +80 -0
- package/ios/BeekonRn.swift +211 -0
- package/ios/Frameworks/BeekonKit.xcframework/Info.plist +44 -0
- package/ios/Frameworks/BeekonKit.xcframework/LICENSE.txt +14 -0
- package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeDirectory +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeResources +233 -0
- package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeSignature +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/PrivacyInfo.xcprivacy +77 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/_CodeSignature/CodeResources +113 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/PrivacyInfo.xcprivacy +77 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +113 -0
- package/lib/module/NativeBeekonRn.js +16 -0
- package/lib/module/NativeBeekonRn.js.map +1 -0
- package/lib/module/beekon.js +107 -0
- package/lib/module/beekon.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/internal/mappers.js +58 -0
- package/lib/module/internal/mappers.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types/config.js +4 -0
- package/lib/module/types/config.js.map +1 -0
- package/lib/module/types/position.js +2 -0
- package/lib/module/types/position.js.map +1 -0
- package/lib/module/types/preset.js +2 -0
- package/lib/module/types/preset.js.map +1 -0
- package/lib/module/types/state.js +2 -0
- package/lib/module/types/state.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts +58 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -0
- package/lib/typescript/src/beekon.d.ts +80 -0
- package/lib/typescript/src/beekon.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/internal/mappers.d.ts +8 -0
- package/lib/typescript/src/internal/mappers.d.ts.map +1 -0
- package/lib/typescript/src/types/config.d.ts +41 -0
- package/lib/typescript/src/types/config.d.ts.map +1 -0
- package/lib/typescript/src/types/position.d.ts +24 -0
- package/lib/typescript/src/types/position.d.ts.map +1 -0
- package/lib/typescript/src/types/preset.d.ts +12 -0
- package/lib/typescript/src/types/preset.d.ts.map +1 -0
- package/lib/typescript/src/types/state.d.ts +22 -0
- package/lib/typescript/src/types/state.d.ts.map +1 -0
- package/package.json +137 -0
- package/scripts/fetch-beekonkit.sh +58 -0
- package/src/NativeBeekonRn.ts +64 -0
- package/src/beekon.ts +110 -0
- package/src/index.tsx +5 -0
- package/src/internal/mappers.ts +50 -0
- package/src/types/config.ts +42 -0
- package/src/types/position.ts +23 -0
- package/src/types/preset.ts +11 -0
- package/src/types/state.ts +16 -0
package/BeekonRn.podspec
ADDED
|
@@ -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,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
package/ios/BeekonRn.mm
ADDED
|
@@ -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
|