@wayq/beekon-rn 0.0.5 → 0.0.7
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 +4 -2
- package/CHANGELOG.md +127 -0
- package/README.md +303 -81
- package/android/build.gradle +3 -2
- package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +164 -7
- package/ios/BeekonRn.mm +23 -0
- package/ios/BeekonRn.swift +199 -10
- package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
- 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/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +111 -3
- 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/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +111 -3
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +111 -3
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
- package/lib/module/NativeBeekonRn.js +20 -0
- package/lib/module/NativeBeekonRn.js.map +1 -1
- package/lib/module/beekon.js +90 -7
- package/lib/module/beekon.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal/mappers.js +98 -4
- package/lib/module/internal/mappers.js.map +1 -1
- package/lib/module/types/auth.js +4 -0
- package/lib/module/types/auth.js.map +1 -0
- package/lib/module/types/error.js +13 -3
- package/lib/module/types/error.js.map +1 -1
- package/lib/module/types/license.js +2 -0
- package/lib/module/types/license.js.map +1 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts +84 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
- package/lib/typescript/src/beekon.d.ts +45 -1
- package/lib/typescript/src/beekon.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal/mappers.d.ts +12 -1
- package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
- package/lib/typescript/src/types/auth.d.ts +99 -0
- package/lib/typescript/src/types/auth.d.ts.map +1 -0
- package/lib/typescript/src/types/config.d.ts +29 -0
- package/lib/typescript/src/types/config.d.ts.map +1 -1
- package/lib/typescript/src/types/enums.d.ts +14 -0
- package/lib/typescript/src/types/enums.d.ts.map +1 -1
- package/lib/typescript/src/types/error.d.ts +14 -4
- package/lib/typescript/src/types/error.d.ts.map +1 -1
- package/lib/typescript/src/types/license.d.ts +50 -0
- package/lib/typescript/src/types/license.d.ts.map +1 -0
- package/package.json +10 -2
- package/scripts/fetch-beekonkit.sh +4 -4
- package/src/NativeBeekonRn.ts +93 -0
- package/src/beekon.ts +104 -6
- package/src/index.tsx +4 -0
- package/src/internal/mappers.ts +109 -1
- package/src/types/auth.ts +101 -0
- package/src/types/config.ts +29 -0
- package/src/types/enums.ts +16 -0
- package/src/types/error.ts +19 -4
- package/src/types/license.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wayq/beekon-rn",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "React Native binding for the Beekon location SDK (Android + iOS).",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"scripts/fetch-beekonkit.sh",
|
|
21
21
|
"*.podspec",
|
|
22
22
|
"LICENSE.txt",
|
|
23
|
+
"CHANGELOG.md",
|
|
23
24
|
"!ios/build",
|
|
24
25
|
"!android/build",
|
|
25
26
|
"!android/gradle",
|
|
@@ -37,7 +38,8 @@
|
|
|
37
38
|
"fetch-beekonkit": "bash scripts/fetch-beekonkit.sh",
|
|
38
39
|
"prepare": "yarn fetch-beekonkit && bob build",
|
|
39
40
|
"typecheck": "tsc",
|
|
40
|
-
"lint": "eslint \"**/*.{js,ts,tsx}\""
|
|
41
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
42
|
+
"test": "jest"
|
|
41
43
|
},
|
|
42
44
|
"keywords": [
|
|
43
45
|
"react-native",
|
|
@@ -45,9 +47,12 @@
|
|
|
45
47
|
"android",
|
|
46
48
|
"location",
|
|
47
49
|
"tracking",
|
|
50
|
+
"location-tracking",
|
|
48
51
|
"gps",
|
|
52
|
+
"gps-tracking",
|
|
49
53
|
"background-location",
|
|
50
54
|
"geolocation",
|
|
55
|
+
"geofencing",
|
|
51
56
|
"beekon",
|
|
52
57
|
"turbo-module"
|
|
53
58
|
],
|
|
@@ -71,12 +76,15 @@
|
|
|
71
76
|
"@eslint/js": "^10.0.1",
|
|
72
77
|
"@react-native/babel-preset": "0.85.0",
|
|
73
78
|
"@react-native/eslint-config": "0.85.0",
|
|
79
|
+
"@types/jest": "^29.5.14",
|
|
74
80
|
"@types/react": "^19.2.0",
|
|
81
|
+
"babel-jest": "^29.7.0",
|
|
75
82
|
"del-cli": "^7.0.0",
|
|
76
83
|
"eslint": "^9.39.4",
|
|
77
84
|
"eslint-config-prettier": "^10.1.8",
|
|
78
85
|
"eslint-plugin-ft-flow": "^3.0.11",
|
|
79
86
|
"eslint-plugin-prettier": "^5.5.5",
|
|
87
|
+
"jest": "^29.7.0",
|
|
80
88
|
"prettier": "^3.8.1",
|
|
81
89
|
"react": "19.2.3",
|
|
82
90
|
"react-native": "0.85.0",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
set -euo pipefail
|
|
15
15
|
|
|
16
|
-
VERSION="0.0.
|
|
16
|
+
VERSION="0.0.7"
|
|
17
17
|
URL="https://github.com/wayqteam/beekon-ios-binary/releases/download/v${VERSION}/BeekonKit.xcframework.zip"
|
|
18
|
-
# SHA256 of the v0.0.
|
|
19
|
-
# `binaryTarget` checksum in beekon-ios-binary's Package.swift at tag v0.0.
|
|
18
|
+
# SHA256 of the v0.0.7 BeekonKit.xcframework.zip. Matches the SwiftPM
|
|
19
|
+
# `binaryTarget` checksum in beekon-ios-binary's Package.swift at tag v0.0.7
|
|
20
20
|
# (SwiftPM's compute-checksum is the SHA256 of the zip).
|
|
21
|
-
EXPECTED_SHA="
|
|
21
|
+
EXPECTED_SHA="48803b540f063f609230f44a0f5b7faca125847db9f4628d0bf5d31d351a762b"
|
|
22
22
|
|
|
23
23
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
24
24
|
DEST_DIR="${ROOT}/ios/Frameworks"
|
package/src/NativeBeekonRn.ts
CHANGED
|
@@ -27,17 +27,47 @@ export type WireKeyValue = {
|
|
|
27
27
|
value: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** Flat wire form of `AuthResponseMapping`. Omitted keys use common-name detection. */
|
|
31
|
+
export type WireAuthResponseMapping = {
|
|
32
|
+
accessToken?: string;
|
|
33
|
+
refreshToken?: string;
|
|
34
|
+
expiresIn?: string;
|
|
35
|
+
expiresAt?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Flat wire form of `AuthConfig` on {@link WireSyncConfig.auth}. Enums travel as
|
|
40
|
+
* strings (`strategy`: 'bearer'|'raw'; `refreshBodyFormat`: 'form'|'json'),
|
|
41
|
+
* string maps as `WireKeyValue[]`, and `expiresAtMs` is epoch milliseconds.
|
|
42
|
+
*/
|
|
43
|
+
export type WireAuthConfig = {
|
|
44
|
+
accessToken?: string;
|
|
45
|
+
refreshToken?: string;
|
|
46
|
+
expiresAtMs?: number;
|
|
47
|
+
strategy: string;
|
|
48
|
+
refreshUrl?: string;
|
|
49
|
+
refreshPayload: WireKeyValue[];
|
|
50
|
+
refreshHeaders: WireKeyValue[];
|
|
51
|
+
refreshBodyFormat: string;
|
|
52
|
+
responseMapping: WireAuthResponseMapping;
|
|
53
|
+
skewMarginSeconds: number;
|
|
54
|
+
seedEpoch?: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
30
57
|
export type WireSyncConfig = {
|
|
31
58
|
url: string;
|
|
32
59
|
headers: WireKeyValue[];
|
|
33
60
|
intervalSeconds: number;
|
|
34
61
|
batchSize: number;
|
|
62
|
+
/** Token-refresh recipe; omitted keeps static-header auth. */
|
|
63
|
+
auth?: WireAuthConfig;
|
|
35
64
|
};
|
|
36
65
|
|
|
37
66
|
/** Android-only foreground-service notification overrides. iOS ignores it. */
|
|
38
67
|
export type WireNotificationConfig = {
|
|
39
68
|
title?: string;
|
|
40
69
|
text?: string;
|
|
70
|
+
smallIcon?: string;
|
|
41
71
|
};
|
|
42
72
|
|
|
43
73
|
export type WireConfig = {
|
|
@@ -54,6 +84,13 @@ export type WireConfig = {
|
|
|
54
84
|
sync?: WireSyncConfig;
|
|
55
85
|
/** Android-only; the iOS native module ignores it. */
|
|
56
86
|
notification?: WireNotificationConfig;
|
|
87
|
+
/**
|
|
88
|
+
* License token (license-format-v1 §9). Omitted/`undefined` means unset — the
|
|
89
|
+
* native SDK falls through to the manifest / Info.plist. The wrapper passes it
|
|
90
|
+
* through verbatim (no trimming or fallback). `configure` crosses as an untyped
|
|
91
|
+
* object, so this field documents the shape rather than altering codegen.
|
|
92
|
+
*/
|
|
93
|
+
licenseKey?: string;
|
|
57
94
|
};
|
|
58
95
|
|
|
59
96
|
export type WireLocation = {
|
|
@@ -112,6 +149,40 @@ export type WireSyncStatus = {
|
|
|
112
149
|
failure?: string;
|
|
113
150
|
};
|
|
114
151
|
|
|
152
|
+
/**
|
|
153
|
+
* A token set the SDK rotated, delivered on `onAuthTokens`. `expiresAtMs` is
|
|
154
|
+
* epoch milliseconds; both it and `refreshToken` are `null` when absent.
|
|
155
|
+
*/
|
|
156
|
+
export type WireAuthTokens = {
|
|
157
|
+
accessToken: string;
|
|
158
|
+
refreshToken: string | null;
|
|
159
|
+
expiresAtMs: number | null;
|
|
160
|
+
epoch: number;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Flat wire form of the current platform's native license status
|
|
165
|
+
* (license-format-v1 §8). `status` is the wire-stable identifier; `tier` and
|
|
166
|
+
* `entitlements` are populated only when `status === 'licensed'` (null / empty
|
|
167
|
+
* otherwise), and `reason` only when `status === 'invalid'`.
|
|
168
|
+
*/
|
|
169
|
+
export type WireLicenseStatus = {
|
|
170
|
+
/**
|
|
171
|
+
* One of: 'notDetermined' | 'licensed' | 'evaluation' | 'expired' |
|
|
172
|
+
* 'updateEntitlementLapsed' | 'invalid'.
|
|
173
|
+
*/
|
|
174
|
+
status: string;
|
|
175
|
+
/** The opaque pricing tier; `null` unless `status === 'licensed'`. */
|
|
176
|
+
tier: string | null;
|
|
177
|
+
/** Opaque feature-gate strings; empty unless `status === 'licensed'`. */
|
|
178
|
+
entitlements: string[];
|
|
179
|
+
/**
|
|
180
|
+
* `null` unless `status === 'invalid'`. One of: 'malformed' | 'unknownKey' |
|
|
181
|
+
* 'badSignature' | 'appIdMismatch' | 'productMismatch' | 'unsupportedVersion'.
|
|
182
|
+
*/
|
|
183
|
+
reason: string | null;
|
|
184
|
+
};
|
|
185
|
+
|
|
115
186
|
export interface Spec extends TurboModule {
|
|
116
187
|
/**
|
|
117
188
|
* The config crosses as an untyped object (`ReadableMap` on Android,
|
|
@@ -125,6 +196,19 @@ export interface Spec extends TurboModule {
|
|
|
125
196
|
stop(): Promise<void>;
|
|
126
197
|
resumeIfNeeded(): Promise<void>;
|
|
127
198
|
|
|
199
|
+
/**
|
|
200
|
+
* One immediate fix. `accuracy` `''` uses the configured mode (a plain
|
|
201
|
+
* `string` rather than `string | null` for portable Codegen, matching
|
|
202
|
+
* `deleteLocations`). Resolves a 0- or 1-element array — empty means timeout /
|
|
203
|
+
* no fix — rather than a nullable struct, which Codegen does not portably
|
|
204
|
+
* support. Rejects with `PERMISSION_DENIED` / `LOCATION_SERVICES_DISABLED` /
|
|
205
|
+
* `LOCATION_UNAVAILABLE` on a precondition failure.
|
|
206
|
+
*/
|
|
207
|
+
getCurrentLocation(
|
|
208
|
+
timeoutMs: number,
|
|
209
|
+
accuracy: string
|
|
210
|
+
): Promise<WireLocation[]>;
|
|
211
|
+
|
|
128
212
|
getLocations(fromMs: number, toMs: number): Promise<WireLocation[]>;
|
|
129
213
|
/**
|
|
130
214
|
* A negative `beforeMs` deletes all stored locations (the wire encoding of
|
|
@@ -141,10 +225,19 @@ export interface Spec extends TurboModule {
|
|
|
141
225
|
removeGeofences(ids: string[]): Promise<void>;
|
|
142
226
|
listGeofences(): Promise<WireGeofence[]>;
|
|
143
227
|
|
|
228
|
+
/**
|
|
229
|
+
* The current platform's native license status (license-format-v1 §8). Each
|
|
230
|
+
* native SDK validates independently; this reflects only the platform you are
|
|
231
|
+
* running on — no cross-platform merging.
|
|
232
|
+
*/
|
|
233
|
+
licenseStatus(): Promise<WireLicenseStatus>;
|
|
234
|
+
|
|
144
235
|
readonly onState: CodegenTypes.EventEmitter<WireState>;
|
|
145
236
|
readonly onLocation: CodegenTypes.EventEmitter<WireLocation>;
|
|
146
237
|
readonly onGeofenceEvent: CodegenTypes.EventEmitter<WireGeofenceEvent>;
|
|
147
238
|
readonly onSyncStatus: CodegenTypes.EventEmitter<WireSyncStatus>;
|
|
239
|
+
readonly onAuthTokens: CodegenTypes.EventEmitter<WireAuthTokens>;
|
|
240
|
+
readonly onLicenseStatus: CodegenTypes.EventEmitter<WireLicenseStatus>;
|
|
148
241
|
}
|
|
149
242
|
|
|
150
243
|
export default TurboModuleRegistry.getEnforcing<Spec>('BeekonRn');
|
package/src/beekon.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import NativeBeekon from './NativeBeekonRn';
|
|
2
|
+
import type { AuthTokens } from './types/auth';
|
|
2
3
|
import type { BeekonConfig } from './types/config';
|
|
4
|
+
import type { AccuracyMode } from './types/enums';
|
|
3
5
|
import type { BeekonGeofence, GeofenceEvent } from './types/geofence';
|
|
6
|
+
import type { LicenseStatus } from './types/license';
|
|
4
7
|
import type { Location } from './types/location';
|
|
5
8
|
import type { BeekonState } from './types/state';
|
|
6
9
|
import type { SyncStatus } from './types/sync';
|
|
@@ -9,8 +12,10 @@ import {
|
|
|
9
12
|
geofenceToWire,
|
|
10
13
|
recordToEntries,
|
|
11
14
|
rethrowAsBeekonError,
|
|
15
|
+
wireToAuthTokens,
|
|
12
16
|
wireToGeofence,
|
|
13
17
|
wireToGeofenceEvent,
|
|
18
|
+
wireToLicenseStatus,
|
|
14
19
|
wireToLocation,
|
|
15
20
|
wireToState,
|
|
16
21
|
wireToSyncStatus,
|
|
@@ -19,11 +24,11 @@ import {
|
|
|
19
24
|
type Listener<T> = (value: T) => void;
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
|
-
* Fans the
|
|
23
|
-
* replay-1 semantics for `state` / `syncStatus` (RN EventEmitters
|
|
24
|
-
* so we cache the latest value and hand it to new subscribers
|
|
25
|
-
* matching the native StateFlow / AsyncStream contract).
|
|
26
|
-
* `geofenceEvent` are plain fan-out with no replay.
|
|
27
|
+
* Fans the five native EventEmitters out to multiple JS subscribers and adds
|
|
28
|
+
* replay-1 semantics for `state` / `syncStatus` / `authTokens` (RN EventEmitters
|
|
29
|
+
* don't replay, so we cache the latest value and hand it to new subscribers
|
|
30
|
+
* immediately — matching the native StateFlow / AsyncStream contract).
|
|
31
|
+
* `location` / `geofenceEvent` are plain fan-out with no replay.
|
|
27
32
|
*
|
|
28
33
|
* Native subscriptions are opened once, on first use, and kept for the app
|
|
29
34
|
* lifetime — the native module collects its flows continuously regardless, so
|
|
@@ -35,8 +40,12 @@ class EventHub {
|
|
|
35
40
|
private readonly locationListeners = new Set<Listener<Location>>();
|
|
36
41
|
private readonly geofenceListeners = new Set<Listener<GeofenceEvent>>();
|
|
37
42
|
private readonly syncListeners = new Set<Listener<SyncStatus>>();
|
|
43
|
+
private readonly authTokenListeners = new Set<Listener<AuthTokens>>();
|
|
44
|
+
private readonly licenseListeners = new Set<Listener<LicenseStatus>>();
|
|
38
45
|
private lastState: BeekonState | undefined;
|
|
39
46
|
private lastSyncStatus: SyncStatus | undefined;
|
|
47
|
+
private lastAuthTokens: AuthTokens | undefined;
|
|
48
|
+
private lastLicenseStatus: LicenseStatus | undefined;
|
|
40
49
|
|
|
41
50
|
/** Idempotent: opens the native subscriptions the first time it is called. */
|
|
42
51
|
ensureSubscribed(): void {
|
|
@@ -60,6 +69,16 @@ class EventHub {
|
|
|
60
69
|
this.lastSyncStatus = s;
|
|
61
70
|
this.syncListeners.forEach((cb) => cb(s));
|
|
62
71
|
});
|
|
72
|
+
NativeBeekon.onAuthTokens((w) => {
|
|
73
|
+
const t = wireToAuthTokens(w);
|
|
74
|
+
this.lastAuthTokens = t;
|
|
75
|
+
this.authTokenListeners.forEach((cb) => cb(t));
|
|
76
|
+
});
|
|
77
|
+
NativeBeekon.onLicenseStatus((w) => {
|
|
78
|
+
const s = wireToLicenseStatus(w);
|
|
79
|
+
this.lastLicenseStatus = s;
|
|
80
|
+
this.licenseListeners.forEach((cb) => cb(s));
|
|
81
|
+
});
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
onState(cb: Listener<BeekonState>): () => void {
|
|
@@ -95,6 +114,24 @@ class EventHub {
|
|
|
95
114
|
this.syncListeners.delete(cb);
|
|
96
115
|
};
|
|
97
116
|
}
|
|
117
|
+
|
|
118
|
+
onAuthTokens(cb: Listener<AuthTokens>): () => void {
|
|
119
|
+
this.ensureSubscribed();
|
|
120
|
+
this.authTokenListeners.add(cb);
|
|
121
|
+
if (this.lastAuthTokens !== undefined) cb(this.lastAuthTokens);
|
|
122
|
+
return () => {
|
|
123
|
+
this.authTokenListeners.delete(cb);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onLicenseStatus(cb: Listener<LicenseStatus>): () => void {
|
|
128
|
+
this.ensureSubscribed();
|
|
129
|
+
this.licenseListeners.add(cb);
|
|
130
|
+
if (this.lastLicenseStatus !== undefined) cb(this.lastLicenseStatus);
|
|
131
|
+
return () => {
|
|
132
|
+
this.licenseListeners.delete(cb);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
/**
|
|
@@ -160,6 +197,37 @@ class BeekonImpl {
|
|
|
160
197
|
await NativeBeekon.resumeIfNeeded();
|
|
161
198
|
}
|
|
162
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Return a single fresh fix on demand — for the moment a host needs an
|
|
202
|
+
* immediate position (e.g. the instant a trip starts).
|
|
203
|
+
*
|
|
204
|
+
* Independent of tracking: works whether or not a session is running and never
|
|
205
|
+
* starts, stops, or disturbs one. The fix is not persisted and does not appear
|
|
206
|
+
* on `onLocation` — it is returned here only, tagged `'manual'`.
|
|
207
|
+
*
|
|
208
|
+
* Resolves `null` if no usable fix arrives within `timeoutMs` (default
|
|
209
|
+
* 15000). `accuracy` overrides the configured mode for this call only;
|
|
210
|
+
* omitted uses the configured mode.
|
|
211
|
+
*
|
|
212
|
+
* Throws `BeekonError` with kind `'permissionDenied'`,
|
|
213
|
+
* `'locationServicesDisabled'`, or `'locationUnavailable'` on a precondition
|
|
214
|
+
* failure.
|
|
215
|
+
*/
|
|
216
|
+
async getCurrentLocation(options?: {
|
|
217
|
+
timeoutMs?: number;
|
|
218
|
+
accuracy?: AccuracyMode;
|
|
219
|
+
}): Promise<Location | null> {
|
|
220
|
+
try {
|
|
221
|
+
const wires = await NativeBeekon.getCurrentLocation(
|
|
222
|
+
options?.timeoutMs ?? 15000,
|
|
223
|
+
options?.accuracy ?? ''
|
|
224
|
+
);
|
|
225
|
+
return wires.length === 0 ? null : wireToLocation(wires[0]!);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
rethrowAsBeekonError(e);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
163
231
|
/**
|
|
164
232
|
* Read persisted fixes in the inclusive range `[from, to]`, oldest first.
|
|
165
233
|
* Reads come from the SDK's local storage (Room on Android, GRDB on iOS) — the
|
|
@@ -181,7 +249,7 @@ class BeekonImpl {
|
|
|
181
249
|
}
|
|
182
250
|
|
|
183
251
|
/**
|
|
184
|
-
* Delete stored locations captured
|
|
252
|
+
* Delete stored locations captured before `before` (all of them when
|
|
185
253
|
* `before` is omitted). Returns the number of rows removed. Throws
|
|
186
254
|
* `BeekonError` with kind `'storage'` on a failure.
|
|
187
255
|
*/
|
|
@@ -243,6 +311,16 @@ class BeekonImpl {
|
|
|
243
311
|
return wires.map(wireToGeofence);
|
|
244
312
|
}
|
|
245
313
|
|
|
314
|
+
/**
|
|
315
|
+
* The current platform's license status (license-format-v1 §8). Android and
|
|
316
|
+
* iOS validate independently — this reflects the platform you are running on,
|
|
317
|
+
* with no cross-platform merging. Purely observational: a license never blocks,
|
|
318
|
+
* degrades, or delays the SDK. For live transitions use {@link onLicenseStatus}.
|
|
319
|
+
*/
|
|
320
|
+
async licenseStatus(): Promise<LicenseStatus> {
|
|
321
|
+
return wireToLicenseStatus(await NativeBeekon.licenseStatus());
|
|
322
|
+
}
|
|
323
|
+
|
|
246
324
|
/**
|
|
247
325
|
* Subscribe to tracking-state transitions (`idle` / `tracking` /
|
|
248
326
|
* `stopped(reason)`). The current state is delivered to new subscribers
|
|
@@ -277,6 +355,26 @@ class BeekonImpl {
|
|
|
277
355
|
onSyncStatus(cb: (s: SyncStatus) => void): () => void {
|
|
278
356
|
return this.hub.onSyncStatus(cb);
|
|
279
357
|
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Subscribe to token rotations the SDK performed during a native refresh (see
|
|
361
|
+
* `SyncConfig.auth`). Mirror them into your own session store. The latest
|
|
362
|
+
* rotation is delivered to new subscribers immediately (replay-1). Sensitive —
|
|
363
|
+
* delivered in-process only, never logged. Returns an unsubscribe function.
|
|
364
|
+
*/
|
|
365
|
+
onAuthTokens(cb: (t: AuthTokens) => void): () => void {
|
|
366
|
+
return this.hub.onAuthTokens(cb);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Subscribe to license-status transitions for the current platform
|
|
371
|
+
* (license-format-v1 §8). The current status is delivered to new subscribers
|
|
372
|
+
* immediately (replay-1), then again on each transition (the first validation
|
|
373
|
+
* is lazy). Purely observational. Returns an unsubscribe function.
|
|
374
|
+
*/
|
|
375
|
+
onLicenseStatus(cb: (s: LicenseStatus) => void): () => void {
|
|
376
|
+
return this.hub.onLicenseStatus(cb);
|
|
377
|
+
}
|
|
280
378
|
}
|
|
281
379
|
|
|
282
380
|
export const Beekon = new BeekonImpl();
|
package/src/index.tsx
CHANGED
|
@@ -12,7 +12,10 @@ export type {
|
|
|
12
12
|
LocationQuality,
|
|
13
13
|
MotionState,
|
|
14
14
|
ActivityType,
|
|
15
|
+
AuthStrategy,
|
|
16
|
+
AuthBodyFormat,
|
|
15
17
|
} from './types/enums';
|
|
18
|
+
export type { AuthConfig, AuthResponseMapping, AuthTokens } from './types/auth';
|
|
16
19
|
export type { Location } from './types/location';
|
|
17
20
|
export type {
|
|
18
21
|
BeekonGeofence,
|
|
@@ -21,4 +24,5 @@ export type {
|
|
|
21
24
|
} from './types/geofence';
|
|
22
25
|
export type { BeekonState, StopReason } from './types/state';
|
|
23
26
|
export type { SyncStatus, SyncFailure } from './types/sync';
|
|
27
|
+
export type { LicenseStatus, LicenseInvalidReason } from './types/license';
|
|
24
28
|
export { BeekonError, type BeekonErrorKind } from './types/error';
|
package/src/internal/mappers.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { AuthConfig, AuthTokens } from '../types/auth';
|
|
1
2
|
import type { BeekonConfig } from '../types/config';
|
|
2
3
|
import type {
|
|
3
4
|
AccuracyMode,
|
|
4
5
|
ActivityType,
|
|
6
|
+
AuthBodyFormat,
|
|
7
|
+
AuthStrategy,
|
|
5
8
|
LocationQuality,
|
|
6
9
|
LocationTrigger,
|
|
7
10
|
MotionState,
|
|
@@ -12,15 +15,19 @@ import type {
|
|
|
12
15
|
GeofenceEvent,
|
|
13
16
|
Transition,
|
|
14
17
|
} from '../types/geofence';
|
|
18
|
+
import type { LicenseInvalidReason, LicenseStatus } from '../types/license';
|
|
15
19
|
import type { Location } from '../types/location';
|
|
16
20
|
import type { BeekonState, StopReason } from '../types/state';
|
|
17
21
|
import type { SyncFailure, SyncStatus } from '../types/sync';
|
|
18
22
|
import { BeekonError, type BeekonErrorKind } from '../types/error';
|
|
19
23
|
import type {
|
|
24
|
+
WireAuthConfig,
|
|
25
|
+
WireAuthTokens,
|
|
20
26
|
WireConfig,
|
|
21
27
|
WireGeofence,
|
|
22
28
|
WireGeofenceEvent,
|
|
23
29
|
WireKeyValue,
|
|
30
|
+
WireLicenseStatus,
|
|
24
31
|
WireLocation,
|
|
25
32
|
WireState,
|
|
26
33
|
WireSyncStatus,
|
|
@@ -37,6 +44,13 @@ const DEFAULTS = {
|
|
|
37
44
|
detectActivity: false,
|
|
38
45
|
syncIntervalSeconds: 300,
|
|
39
46
|
syncBatchSize: 100,
|
|
47
|
+
authStrategy: 'bearer' as AuthStrategy,
|
|
48
|
+
authBodyFormat: 'form' as AuthBodyFormat,
|
|
49
|
+
authSkewMarginSeconds: 60,
|
|
50
|
+
authRefreshHeaders: { Authorization: 'Bearer {accessToken}' } as Record<
|
|
51
|
+
string,
|
|
52
|
+
string
|
|
53
|
+
>,
|
|
40
54
|
};
|
|
41
55
|
|
|
42
56
|
// --- Public → wire ---------------------------------------------------------
|
|
@@ -68,11 +82,20 @@ export function configToWire(config: BeekonConfig): WireConfig {
|
|
|
68
82
|
headers: recordToEntries(sync.headers),
|
|
69
83
|
intervalSeconds: sync.intervalSeconds ?? DEFAULTS.syncIntervalSeconds,
|
|
70
84
|
batchSize: sync.batchSize ?? DEFAULTS.syncBatchSize,
|
|
85
|
+
auth: sync.auth ? authToWire(sync.auth) : undefined,
|
|
71
86
|
}
|
|
72
87
|
: undefined,
|
|
73
88
|
notification: config.notification
|
|
74
|
-
? {
|
|
89
|
+
? {
|
|
90
|
+
title: config.notification.title,
|
|
91
|
+
text: config.notification.text,
|
|
92
|
+
smallIcon: config.notification.smallIcon,
|
|
93
|
+
}
|
|
75
94
|
: undefined,
|
|
95
|
+
// Passed through verbatim — `undefined` is omitted and the native SDK falls
|
|
96
|
+
// through to manifest/Info.plist. No wrapper-side trimming or fallback
|
|
97
|
+
// (blank/whitespace handling lives in the native SDK; spec §9).
|
|
98
|
+
licenseKey: config.licenseKey,
|
|
76
99
|
};
|
|
77
100
|
}
|
|
78
101
|
|
|
@@ -87,6 +110,31 @@ export function geofenceToWire(g: BeekonGeofence): WireGeofence {
|
|
|
87
110
|
};
|
|
88
111
|
}
|
|
89
112
|
|
|
113
|
+
// `expiresAt` (a `Date`) becomes epoch millis on the wire; the native side
|
|
114
|
+
// converts to epoch seconds. String maps travel as `WireKeyValue[]`.
|
|
115
|
+
export function authToWire(a: AuthConfig): WireAuthConfig {
|
|
116
|
+
return {
|
|
117
|
+
accessToken: a.accessToken,
|
|
118
|
+
refreshToken: a.refreshToken,
|
|
119
|
+
expiresAtMs: a.expiresAt?.getTime(),
|
|
120
|
+
strategy: a.strategy ?? DEFAULTS.authStrategy,
|
|
121
|
+
refreshUrl: a.refreshUrl,
|
|
122
|
+
refreshPayload: recordToEntries(a.refreshPayload),
|
|
123
|
+
refreshHeaders: recordToEntries(
|
|
124
|
+
a.refreshHeaders ?? DEFAULTS.authRefreshHeaders
|
|
125
|
+
),
|
|
126
|
+
refreshBodyFormat: a.refreshBodyFormat ?? DEFAULTS.authBodyFormat,
|
|
127
|
+
responseMapping: {
|
|
128
|
+
accessToken: a.responseMapping?.accessToken,
|
|
129
|
+
refreshToken: a.responseMapping?.refreshToken,
|
|
130
|
+
expiresIn: a.responseMapping?.expiresIn,
|
|
131
|
+
expiresAt: a.responseMapping?.expiresAt,
|
|
132
|
+
},
|
|
133
|
+
skewMarginSeconds: a.skewMarginSeconds ?? DEFAULTS.authSkewMarginSeconds,
|
|
134
|
+
seedEpoch: a.seedEpoch,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
90
138
|
// --- Wire → public ---------------------------------------------------------
|
|
91
139
|
|
|
92
140
|
export function wireToLocation(w: WireLocation): Location {
|
|
@@ -180,6 +228,60 @@ export function wireToSyncStatus(w: WireSyncStatus): SyncStatus {
|
|
|
180
228
|
}
|
|
181
229
|
}
|
|
182
230
|
|
|
231
|
+
/** `expiresAtMs` (epoch millis) becomes a `Date`; `null` propagates faithfully. */
|
|
232
|
+
export function wireToAuthTokens(w: WireAuthTokens): AuthTokens {
|
|
233
|
+
return {
|
|
234
|
+
accessToken: w.accessToken,
|
|
235
|
+
refreshToken: w.refreshToken,
|
|
236
|
+
expiresAt: w.expiresAtMs == null ? null : new Date(w.expiresAtMs),
|
|
237
|
+
epoch: w.epoch,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Decode the wire-stable license status into the public discriminated union. The
|
|
243
|
+
* native side already decided the status (the wrapper performs no validation) —
|
|
244
|
+
* this only re-shapes the flat wire form and validates the enum strings.
|
|
245
|
+
*/
|
|
246
|
+
export function wireToLicenseStatus(w: WireLicenseStatus): LicenseStatus {
|
|
247
|
+
switch (w.status) {
|
|
248
|
+
case 'licensed':
|
|
249
|
+
return {
|
|
250
|
+
status: 'licensed',
|
|
251
|
+
tier: w.tier ?? '',
|
|
252
|
+
entitlements: w.entitlements ?? [],
|
|
253
|
+
};
|
|
254
|
+
case 'invalid':
|
|
255
|
+
return {
|
|
256
|
+
status: 'invalid',
|
|
257
|
+
reason: oneOf<LicenseInvalidReason>(
|
|
258
|
+
w.reason,
|
|
259
|
+
[
|
|
260
|
+
'malformed',
|
|
261
|
+
'unknownKey',
|
|
262
|
+
'badSignature',
|
|
263
|
+
'appIdMismatch',
|
|
264
|
+
'productMismatch',
|
|
265
|
+
'unsupportedVersion',
|
|
266
|
+
],
|
|
267
|
+
'malformed'
|
|
268
|
+
),
|
|
269
|
+
};
|
|
270
|
+
case 'evaluation':
|
|
271
|
+
return { status: 'evaluation' };
|
|
272
|
+
case 'expired':
|
|
273
|
+
return { status: 'expired' };
|
|
274
|
+
case 'updateEntitlementLapsed':
|
|
275
|
+
return { status: 'updateEntitlementLapsed' };
|
|
276
|
+
case 'notDetermined':
|
|
277
|
+
return { status: 'notDetermined' };
|
|
278
|
+
default:
|
|
279
|
+
// Forward-compat: an unknown status string means a native build newer than
|
|
280
|
+
// this wrapper — surface as notDetermined rather than throwing.
|
|
281
|
+
return { status: 'notDetermined' };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
183
285
|
function toStopReason(s: string | undefined): StopReason {
|
|
184
286
|
return oneOf<StopReason>(
|
|
185
287
|
s,
|
|
@@ -235,6 +337,12 @@ function codeToKind(code: string | undefined): BeekonErrorKind | undefined {
|
|
|
235
337
|
return 'storage';
|
|
236
338
|
case 'INVALID_GEOFENCE':
|
|
237
339
|
return 'invalidGeofence';
|
|
340
|
+
case 'PERMISSION_DENIED':
|
|
341
|
+
return 'permissionDenied';
|
|
342
|
+
case 'LOCATION_SERVICES_DISABLED':
|
|
343
|
+
return 'locationServicesDisabled';
|
|
344
|
+
case 'LOCATION_UNAVAILABLE':
|
|
345
|
+
return 'locationUnavailable';
|
|
238
346
|
default:
|
|
239
347
|
return undefined;
|
|
240
348
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { AuthBodyFormat, AuthStrategy } from './enums';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where the SDK reads rotated tokens from the JSON response to a refresh
|
|
5
|
+
* request. Mirrors the native `AuthResponseMapping` (iOS) / `ResponseMapping`
|
|
6
|
+
* (Android).
|
|
7
|
+
*
|
|
8
|
+
* Each value is a response key. An omitted field falls back to common-name
|
|
9
|
+
* detection: `access_token`/`accessToken`/`token` for the access token,
|
|
10
|
+
* `refresh_token`/`refreshToken` for a rotated refresh token, and `expires_in`
|
|
11
|
+
* (relative seconds) or `expires_at`/`expires` (absolute epoch seconds) for the
|
|
12
|
+
* expiry. The default (all-omitted) is pure common-name detection.
|
|
13
|
+
*/
|
|
14
|
+
export type AuthResponseMapping = {
|
|
15
|
+
/** Response key holding the new access token. Omitted uses common-name detection. */
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
/** Response key holding a rotated refresh token. Omitted uses common-name detection. */
|
|
18
|
+
refreshToken?: string;
|
|
19
|
+
/** Response key holding a relative lifetime in seconds. Omitted uses detection. */
|
|
20
|
+
expiresIn?: string;
|
|
21
|
+
/** Response key holding an absolute expiry in epoch seconds. Omitted uses detection. */
|
|
22
|
+
expiresAt?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Declarative recipe for native token refresh, set on `SyncConfig.auth`.
|
|
27
|
+
* Mirrors the native `AuthConfig`.
|
|
28
|
+
*
|
|
29
|
+
* When present, the SDK attaches the access token to every upload (per
|
|
30
|
+
* {@link AuthConfig.strategy}), refreshes it **proactively** before
|
|
31
|
+
* {@link AuthConfig.expiresAt} and **reactively** on a `401`/`403`, then retries
|
|
32
|
+
* the upload — all natively, so it works in the background and on a cold launch
|
|
33
|
+
* with no host involvement. Omit {@link AuthConfig.refreshUrl} to disable
|
|
34
|
+
* refresh and keep the legacy behaviour (a `401`/`403` pauses sync and surfaces
|
|
35
|
+
* `SyncFailure 'auth'`).
|
|
36
|
+
*
|
|
37
|
+
* **Tokens are a seed, not config.** {@link AuthConfig.accessToken},
|
|
38
|
+
* {@link AuthConfig.refreshToken} and {@link AuthConfig.expiresAt} are supplied
|
|
39
|
+
* once; the SDK then owns and rotates the live token set in secure storage
|
|
40
|
+
* (Keychain / encrypted prefs). Observe rotations via `Beekon.onAuthTokens()` to
|
|
41
|
+
* mirror the tokens into your own session store.
|
|
42
|
+
*
|
|
43
|
+
* Requires the native SDK ≥ 0.0.6; with an older native binary the recipe is
|
|
44
|
+
* ignored and only static `SyncConfig.headers` apply.
|
|
45
|
+
*/
|
|
46
|
+
export type AuthConfig = {
|
|
47
|
+
/** Initial access token. A seed: the SDK owns and rotates it after first persist. */
|
|
48
|
+
accessToken?: string;
|
|
49
|
+
/** Initial refresh token POSTed to {@link AuthConfig.refreshUrl}. A seed. */
|
|
50
|
+
refreshToken?: string;
|
|
51
|
+
/** Absolute expiry of {@link AuthConfig.accessToken}. Omitted disables proactive refresh. */
|
|
52
|
+
expiresAt?: Date;
|
|
53
|
+
/** How the access token is attached. Default: `'bearer'`. */
|
|
54
|
+
strategy?: AuthStrategy;
|
|
55
|
+
/** Authorization server's refresh endpoint. Omitted disables refresh. */
|
|
56
|
+
refreshUrl?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Form fields POSTed to {@link AuthConfig.refreshUrl}. A value containing
|
|
59
|
+
* `{refreshToken}` is substituted with the current refresh token. Default: empty.
|
|
60
|
+
*/
|
|
61
|
+
refreshPayload?: Record<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Headers sent on the refresh request. A value containing `{accessToken}` is
|
|
64
|
+
* substituted with the current access token. Default:
|
|
65
|
+
* `{ Authorization: 'Bearer {accessToken}' }`.
|
|
66
|
+
*/
|
|
67
|
+
refreshHeaders?: Record<string, string>;
|
|
68
|
+
/** Encoding of the refresh request body. Default: `'form'`. */
|
|
69
|
+
refreshBodyFormat?: AuthBodyFormat;
|
|
70
|
+
/** Where to read rotated tokens from the refresh response. Default: detection. */
|
|
71
|
+
responseMapping?: AuthResponseMapping;
|
|
72
|
+
/** Seconds before {@link AuthConfig.expiresAt} at which a proactive refresh fires. Default: `60`. */
|
|
73
|
+
skewMarginSeconds?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Optional monotonic re-seed signal. When greater than the SDK's stored epoch,
|
|
76
|
+
* a re-supplied seed replaces the rotated token set (e.g. after the user
|
|
77
|
+
* re-authenticates). Omitted adopts a re-supplied seed only when no token has
|
|
78
|
+
* been stored yet.
|
|
79
|
+
*/
|
|
80
|
+
seedEpoch?: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A token set the SDK rotated, delivered via `Beekon.onAuthTokens()`. Mirrors
|
|
85
|
+
* the native `AuthTokens` (iOS) / `TokenRefresh` (Android).
|
|
86
|
+
*
|
|
87
|
+
* Treat these as sensitive: they are delivered in-process only and are never
|
|
88
|
+
* logged or persisted to plaintext. Use them to mirror the SDK's current
|
|
89
|
+
* credentials into your own session store. The latest rotation is replayed to
|
|
90
|
+
* new subscribers (replay-1).
|
|
91
|
+
*/
|
|
92
|
+
export type AuthTokens = {
|
|
93
|
+
/** The rotated access token now in use. */
|
|
94
|
+
accessToken: string;
|
|
95
|
+
/** The current refresh token (rotated if the server returned a new one), or `null`. */
|
|
96
|
+
refreshToken: string | null;
|
|
97
|
+
/** Absolute expiry of {@link AuthTokens.accessToken}, or `null` if the response carried none. */
|
|
98
|
+
expiresAt: Date | null;
|
|
99
|
+
/** The SDK's monotonic token generation, incremented on each successful refresh. */
|
|
100
|
+
epoch: number;
|
|
101
|
+
};
|