@wayq/beekon-rn 0.0.8 → 0.1.0

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 +3 -3
  2. package/CHANGELOG.md +58 -6
  3. package/LICENSE.txt +3 -3
  4. package/README.md +85 -264
  5. package/android/build.gradle +2 -2
  6. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +133 -8
  7. package/ios/BeekonRn.mm +40 -0
  8. package/ios/BeekonRn.swift +176 -8
  9. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
  10. package/ios/Frameworks/BeekonKit.xcframework/LICENSE.txt +3 -3
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +5839 -3482
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +93 -16
  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/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +5839 -3482
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +93 -16
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5839 -3482
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +93 -16
  22. package/lib/module/NativeBeekonRn.js +6 -0
  23. package/lib/module/NativeBeekonRn.js.map +1 -1
  24. package/lib/module/beekon.js +109 -1
  25. package/lib/module/beekon.js.map +1 -1
  26. package/lib/module/index.js +4 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/internal/licenseNudge.js +64 -0
  29. package/lib/module/internal/licenseNudge.js.map +1 -0
  30. package/lib/module/internal/mappers.js +68 -8
  31. package/lib/module/internal/mappers.js.map +1 -1
  32. package/lib/module/types/config.js +73 -1
  33. package/lib/module/types/config.js.map +1 -1
  34. package/lib/module/types/log.js +4 -0
  35. package/lib/module/types/log.js.map +1 -0
  36. package/lib/module/types/permission.js +2 -0
  37. package/lib/module/types/permission.js.map +1 -0
  38. package/lib/typescript/src/NativeBeekonRn.d.ts +66 -2
  39. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  40. package/lib/typescript/src/beekon.d.ts +56 -1
  41. package/lib/typescript/src/beekon.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +5 -2
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/internal/licenseNudge.d.ts +38 -0
  45. package/lib/typescript/src/internal/licenseNudge.d.ts.map +1 -0
  46. package/lib/typescript/src/internal/mappers.d.ts +5 -1
  47. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  48. package/lib/typescript/src/types/config.d.ts +90 -7
  49. package/lib/typescript/src/types/config.d.ts.map +1 -1
  50. package/lib/typescript/src/types/enums.d.ts +8 -0
  51. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  52. package/lib/typescript/src/types/log.d.ts +22 -0
  53. package/lib/typescript/src/types/log.d.ts.map +1 -0
  54. package/lib/typescript/src/types/permission.d.ts +42 -0
  55. package/lib/typescript/src/types/permission.d.ts.map +1 -0
  56. package/lib/typescript/src/types/state.d.ts +4 -1
  57. package/lib/typescript/src/types/state.d.ts.map +1 -1
  58. package/package.json +5 -5
  59. package/scripts/fetch-beekonkit.sh +6 -6
  60. package/src/NativeBeekonRn.ts +70 -2
  61. package/src/beekon.ts +114 -1
  62. package/src/index.tsx +12 -1
  63. package/src/internal/licenseNudge.ts +80 -0
  64. package/src/internal/mappers.ts +93 -7
  65. package/src/types/config.ts +99 -7
  66. package/src/types/enums.ts +9 -0
  67. package/src/types/log.ts +22 -0
  68. package/src/types/permission.ts +48 -0
  69. package/src/types/state.ts +4 -0
@@ -7,9 +7,11 @@ import type {
7
7
  AuthStrategy,
8
8
  LocationQuality,
9
9
  LocationTrigger,
10
+ LogLevel,
10
11
  MotionState,
11
12
  StationaryMode,
12
13
  } from '../types/enums';
14
+ import type { LogEntry } from '../types/log';
13
15
  import type {
14
16
  BeekonGeofence,
15
17
  GeofenceEvent,
@@ -17,6 +19,11 @@ import type {
17
19
  } from '../types/geofence';
18
20
  import type { LicenseInvalidReason, LicenseStatus } from '../types/license';
19
21
  import type { Location } from '../types/location';
22
+ import type {
23
+ PermissionAccuracy,
24
+ PermissionLevel,
25
+ PermissionStatus,
26
+ } from '../types/permission';
20
27
  import type { BeekonState, StopReason } from '../types/state';
21
28
  import type { SyncFailure, SyncStatus } from '../types/sync';
22
29
  import { BeekonError, type BeekonErrorKind } from '../types/error';
@@ -29,6 +36,8 @@ import type {
29
36
  WireKeyValue,
30
37
  WireLicenseStatus,
31
38
  WireLocation,
39
+ WireLogEntry,
40
+ WirePermissionStatus,
32
41
  WireState,
33
42
  WireSyncStatus,
34
43
  } from '../NativeBeekonRn';
@@ -44,6 +53,7 @@ const DEFAULTS = {
44
53
  detectActivity: false,
45
54
  syncIntervalSeconds: 300,
46
55
  syncBatchSize: 100,
56
+ syncThreshold: 0,
47
57
  authStrategy: 'bearer' as AuthStrategy,
48
58
  authBodyFormat: 'form' as AuthBodyFormat,
49
59
  authSkewMarginSeconds: 60,
@@ -51,6 +61,7 @@ const DEFAULTS = {
51
61
  string,
52
62
  string
53
63
  >,
64
+ logLevel: 'info' as LogLevel,
54
65
  };
55
66
 
56
67
  // --- Public → wire ---------------------------------------------------------
@@ -62,9 +73,53 @@ export function recordToEntries(
62
73
  return Object.keys(record).map((key) => ({ key, value: record[key]! }));
63
74
  }
64
75
 
76
+ function notificationToWire(config: BeekonConfig): WireConfig['notification'] {
77
+ return config.notification
78
+ ? {
79
+ title: config.notification.title,
80
+ text: config.notification.text,
81
+ smallIcon: config.notification.smallIcon,
82
+ }
83
+ : undefined;
84
+ }
85
+
86
+ // Switches on the sealed-config arm (cloud-mode-v1 §2). The wire form is flat
87
+ // and carries both arms' slots; only the active arm's fields are populated. The
88
+ // tracking-param slots are filled with defaults regardless so the wire struct
89
+ // stays fully-populated — the native cloud arm ignores them and derives config
90
+ // from the server.
65
91
  export function configToWire(config: BeekonConfig): WireConfig {
92
+ const tracking = {
93
+ minTimeBetweenLocationsSeconds: DEFAULTS.minTimeBetweenLocationsSeconds,
94
+ minDistanceBetweenLocationsMeters:
95
+ DEFAULTS.minDistanceBetweenLocationsMeters,
96
+ accuracyMode: DEFAULTS.accuracyMode,
97
+ whenStationary: DEFAULTS.whenStationary,
98
+ stationaryRadiusMeters: DEFAULTS.stationaryRadiusMeters,
99
+ detectActivity: DEFAULTS.detectActivity,
100
+ };
101
+
102
+ if (config.mode === 'cloud') {
103
+ return {
104
+ mode: 'cloud',
105
+ // Passed through verbatim; the native cloud arm trims and validates it.
106
+ projectKey: config.projectKey,
107
+ // Omitted means the native baked-in default (https://api.getbeekon.com).
108
+ endpoint: config.endpoint,
109
+ ...tracking,
110
+ // Cloud config is server-owned: no local sync or license key.
111
+ sync: undefined,
112
+ notification: notificationToWire(config),
113
+ licenseKey: undefined,
114
+ logLevel: config.logLevel ?? DEFAULTS.logLevel,
115
+ };
116
+ }
117
+
66
118
  const sync = config.sync;
67
119
  return {
120
+ mode: 'selfManaged',
121
+ projectKey: undefined,
122
+ endpoint: undefined,
68
123
  minTimeBetweenLocationsSeconds:
69
124
  config.minTimeBetweenLocationsSeconds ??
70
125
  DEFAULTS.minTimeBetweenLocationsSeconds,
@@ -82,20 +137,16 @@ export function configToWire(config: BeekonConfig): WireConfig {
82
137
  headers: recordToEntries(sync.headers),
83
138
  intervalSeconds: sync.intervalSeconds ?? DEFAULTS.syncIntervalSeconds,
84
139
  batchSize: sync.batchSize ?? DEFAULTS.syncBatchSize,
140
+ syncThreshold: sync.syncThreshold ?? DEFAULTS.syncThreshold,
85
141
  auth: sync.auth ? authToWire(sync.auth) : undefined,
86
142
  }
87
143
  : undefined,
88
- notification: config.notification
89
- ? {
90
- title: config.notification.title,
91
- text: config.notification.text,
92
- smallIcon: config.notification.smallIcon,
93
- }
94
- : undefined,
144
+ notification: notificationToWire(config),
95
145
  // Passed through verbatim — `undefined` is omitted and the native SDK falls
96
146
  // through to manifest/Info.plist. No wrapper-side trimming or fallback
97
147
  // (blank/whitespace handling lives in the native SDK; spec §9).
98
148
  licenseKey: config.licenseKey,
149
+ logLevel: config.logLevel ?? DEFAULTS.logLevel,
99
150
  };
100
151
  }
101
152
 
@@ -201,6 +252,20 @@ export function wireToGeofenceEvent(w: WireGeofenceEvent): GeofenceEvent {
201
252
  };
202
253
  }
203
254
 
255
+ export function wireToLogEntry(w: WireLogEntry): LogEntry {
256
+ return {
257
+ id: w.id,
258
+ timestamp: new Date(w.timestampMs),
259
+ level: oneOf<LogLevel>(
260
+ w.level,
261
+ ['off', 'error', 'warn', 'info', 'debug', 'verbose'],
262
+ 'info'
263
+ ),
264
+ category: w.category,
265
+ message: w.message,
266
+ };
267
+ }
268
+
204
269
  export function wireToState(w: WireState): BeekonState {
205
270
  switch (w.type) {
206
271
  case 'idle':
@@ -215,6 +280,26 @@ export function wireToState(w: WireState): BeekonState {
215
280
  }
216
281
  }
217
282
 
283
+ export function wireToPermissionStatus(
284
+ w: WirePermissionStatus
285
+ ): PermissionStatus {
286
+ const level = oneOf<PermissionLevel>(
287
+ w.level,
288
+ ['notDetermined', 'denied', 'restricted', 'foreground', 'background'],
289
+ 'notDetermined'
290
+ );
291
+ // '' is the wire encoding of null (not granted); an unknown non-empty value
292
+ // degrades to null rather than guessing a precision.
293
+ const accuracy: PermissionAccuracy | null =
294
+ w.accuracy === 'full' || w.accuracy === 'reduced' ? w.accuracy : null;
295
+ return {
296
+ level,
297
+ accuracy,
298
+ isAuthorized: level === 'foreground' || level === 'background',
299
+ canTrackInBackground: level === 'background',
300
+ };
301
+ }
302
+
218
303
  export function wireToSyncStatus(w: WireSyncStatus): SyncStatus {
219
304
  switch (w.type) {
220
305
  case 'idle':
@@ -290,6 +375,7 @@ function toStopReason(s: string | undefined): StopReason {
290
375
  'permissionDenied',
291
376
  'locationServicesDisabled',
292
377
  'locationUnavailable',
378
+ 'cloudModeUnavailable',
293
379
  'system',
294
380
  ],
295
381
  // The native side always populates the reason for a `stopped` state; an
@@ -1,5 +1,5 @@
1
1
  import type { AuthConfig } from './auth';
2
- import type { AccuracyMode, StationaryMode } from './enums';
2
+ import type { AccuracyMode, LogLevel, StationaryMode } from './enums';
3
3
 
4
4
  /**
5
5
  * Foreground-service notification overrides. **Android-only** — the platform
@@ -23,8 +23,8 @@ export type NotificationConfig = {
23
23
  };
24
24
 
25
25
  /**
26
- * Server-upload configuration. Presence of {@link BeekonConfig.sync} turns on
27
- * batched uploads of stored locations and geofence events; omitting it keeps
26
+ * Server-upload configuration. Presence of {@link SelfManagedConfig.sync} turns
27
+ * on batched uploads of stored locations and geofence events; omitting it keeps
28
28
  * tracking local-only.
29
29
  */
30
30
  export type SyncConfig = {
@@ -39,6 +39,15 @@ export type SyncConfig = {
39
39
  intervalSeconds?: number;
40
40
  /** Maximum locations per upload. Clamped to `[1, 1000]`. Default: `100`. */
41
41
  batchSize?: number;
42
+ /**
43
+ * Pending-fix count that triggers an early upload of regular fixes, ahead of
44
+ * the {@link SyncConfig.intervalSeconds} schedule (not subject to the Android
45
+ * ~15 min floor). `0` (the default) leaves regular fixes to the schedule.
46
+ * Negative values are treated as `0`. Independent of this setting, a geofence
47
+ * event and a session stop with pending fixes always flush immediately while
48
+ * sync is configured. Requires the native SDK ≥ 0.0.9. Default: `0`.
49
+ */
50
+ syncThreshold?: number;
42
51
  /**
43
52
  * Declarative token-refresh recipe. When set, the SDK attaches and natively
44
53
  * refreshes the access token (proactively before expiry, reactively on
@@ -49,15 +58,58 @@ export type SyncConfig = {
49
58
  };
50
59
 
51
60
  /**
52
- * Tracking configuration. Every field is optional — `Beekon.start()` falls back
53
- * to the previously persisted config, or to the documented defaults below if
54
- * never configured.
61
+ * **Cloud-mode** configuration (cloud-mode-v1 §2). Point the SDK at Beekon
62
+ * Cloud (or a self-hosted Beekon backend) with a project key; the **server**
63
+ * owns the tracking parameters, sync cadence, geofences, and the license. This
64
+ * arm therefore exposes **only** the project credential, an optional endpoint
65
+ * override, the platform-universal Android `notification`, and `logLevel` — it
66
+ * deliberately does NOT expose tracking params, `sync`, or `licenseKey` (those
67
+ * are server-owned or derived).
68
+ */
69
+ export type CloudConfig = {
70
+ /** Arm discriminator — selects cloud mode. */
71
+ mode: 'cloud';
72
+ /**
73
+ * Opaque project credential (prefix `bkproj_`). A **bearer secret** on a
74
+ * separate credential plane from the license token — do **not** commit it to
75
+ * source control. Surrounding whitespace is trimmed and an empty key is
76
+ * rejected natively.
77
+ */
78
+ projectKey: string;
79
+ /**
80
+ * Base URL of the Beekon backend. Omit for Beekon Cloud (the SDK uses the
81
+ * baked-in `https://api.getbeekon.com`); set it to a self-hosted Beekon
82
+ * backend's base URL (the `/v1/token`, `/v1/config`, `/v1/ingest` suffixes
83
+ * are derived). Default: unset (`https://api.getbeekon.com`).
84
+ */
85
+ endpoint?: string;
86
+ /** Android-only notification overrides. Ignored on iOS. */
87
+ notification?: NotificationConfig;
88
+ /**
89
+ * Diagnostic log verbosity threshold. See {@link SelfManagedConfig.logLevel}.
90
+ * Default: `'info'`.
91
+ */
92
+ logLevel?: LogLevel;
93
+ };
94
+
95
+ /**
96
+ * **Self-managed** configuration (cloud-mode-v1 §2 / self-managed-mode-v1).
97
+ * The app owns all configuration locally — tracking parameters, an optional
98
+ * `sync` (omit ⇒ local-only; set ⇒ custom-backend ingest), an optional
99
+ * `licenseKey`, plus `notification` and `logLevel`. This is the pre-cloud
100
+ * behaviour.
101
+ *
102
+ * Every field other than `mode` is optional — `Beekon.start()` falls back to
103
+ * the previously persisted config, or to the documented defaults below if never
104
+ * configured.
55
105
  *
56
106
  * The two gate knobs (`minTimeBetweenLocationsSeconds`,
57
107
  * `minDistanceBetweenLocationsMeters`) admit a fix only when **both** are
58
108
  * satisfied; pass `0` to disable a gate.
59
109
  */
60
- export type BeekonConfig = {
110
+ export type SelfManagedConfig = {
111
+ /** Arm discriminator — selects self-managed mode. */
112
+ mode: 'selfManaged';
61
113
  /** Minimum seconds between admitted fixes (10s floor). Default: `30`. */
62
114
  minTimeBetweenLocationsSeconds?: number;
63
115
  /** Minimum metres between admitted fixes. `0` disables it. Default: `100`. */
@@ -91,4 +143,44 @@ export type BeekonConfig = {
91
143
  * useless to anyone else. Default: unset.
92
144
  */
93
145
  licenseKey?: string;
146
+ /**
147
+ * Diagnostic log verbosity threshold. Entries at or below this level are
148
+ * recorded to the persisted ring buffer and {@link Beekon.onLog}. `'info'`
149
+ * (the default) keeps coordinates out of the buffer — they appear only at
150
+ * `'debug'`/`'verbose'`. Change at runtime with {@link Beekon.setLogLevel}.
151
+ * Requires the native SDK ≥ 0.0.9. Default: `'info'`.
152
+ */
153
+ logLevel?: LogLevel;
94
154
  };
155
+
156
+ /**
157
+ * Tracking configuration. A **sealed two-arm** discriminated union (cloud-mode-v1
158
+ * §2): the `mode` tag selects {@link CloudConfig} (server-owned config) or
159
+ * {@link SelfManagedConfig} (app-owned config). Each arm exposes only that arm's
160
+ * fields — the cloud arm cannot carry tracking params / sync / a license key.
161
+ *
162
+ * Build a value with the {@link BeekonConfig.cloud} / {@link BeekonConfig.selfManaged}
163
+ * factories, or with a plain object literal that includes the `mode` tag.
164
+ */
165
+ export type BeekonConfig = CloudConfig | SelfManagedConfig;
166
+
167
+ /**
168
+ * Factory helpers mirroring the native sealed-type constructors
169
+ * (`BeekonConfig.cloud(...)` / `BeekonConfig.selfManaged(...)`). They stamp the
170
+ * `mode` discriminator so each call site can express only its arm's fields.
171
+ *
172
+ * Merged with the {@link BeekonConfig} type so `BeekonConfig` is usable as both
173
+ * a type and a value.
174
+ */
175
+ export const BeekonConfig = {
176
+ /** Build a {@link CloudConfig}. `projectKey` is required. */
177
+ cloud(options: Omit<CloudConfig, 'mode'>): CloudConfig {
178
+ return { mode: 'cloud', ...options };
179
+ },
180
+ /** Build a {@link SelfManagedConfig}. Every field is optional. */
181
+ selfManaged(
182
+ options: Omit<SelfManagedConfig, 'mode'> = {}
183
+ ): SelfManagedConfig {
184
+ return { mode: 'selfManaged', ...options };
185
+ },
186
+ } as const;
@@ -78,3 +78,12 @@ export type AuthStrategy = 'bearer' | 'raw';
78
78
  * - `'json'` — `application/json`.
79
79
  */
80
80
  export type AuthBodyFormat = 'form' | 'json';
81
+
82
+ /**
83
+ * Diagnostic log verbosity threshold. Ordered by increasing verbosity; an entry
84
+ * is recorded only when its level is at or below the active threshold
85
+ * ({@link BeekonConfig.logLevel} or {@link Beekon.setLogLevel}). The default is
86
+ * `'info'`, so coordinates (logged only at `'debug'`/`'verbose'`) stay out of the
87
+ * buffer. Mirrors the native `LogLevel`; see `spec/diagnostics/log-format-v1`.
88
+ */
89
+ export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug' | 'verbose';
@@ -0,0 +1,22 @@
1
+ import type { LogLevel } from './enums';
2
+
3
+ /**
4
+ * A single diagnostic log record from the Beekon SDK.
5
+ *
6
+ * Delivered live on {@link Beekon.onLog} and read back from the persisted ring
7
+ * buffer via {@link Beekon.getLog}. Entries survive process death — the point of
8
+ * the buffer is post-hoc field diagnosis. Mirrors the native `LogEntry`; see
9
+ * `spec/diagnostics/log-format-v1`.
10
+ */
11
+ export type LogEntry = {
12
+ /** UUIDv7 — orders and dedups entries. */
13
+ id: string;
14
+ /** When the entry was recorded. */
15
+ timestamp: Date;
16
+ /** Severity. Always one of `'error'`…`'verbose'`. */
17
+ level: LogLevel;
18
+ /** Originating subsystem (e.g. `'location'`, `'sync'`); host breadcrumbs use `'app'`. */
19
+ category: string;
20
+ /** Human-readable, already-redacted text. */
21
+ message: string;
22
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * How far the host app's location grant reaches. Mirrors the native
3
+ * `PermissionStatus` level on both platforms.
4
+ *
5
+ * Normalized — not platform permission constants. `'restricted'` is **iOS-only**
6
+ * (MDM / Screen Time); on Android "not yet asked" and "denied" cannot be told
7
+ * apart without an `Activity`, so both report `'notDetermined'`.
8
+ *
9
+ * - `'notDetermined'` — not yet decided; request to proceed.
10
+ * - `'denied'` — the user denied access (iOS). User-fixable in Settings.
11
+ * - `'restricted'` — blocked by an unchangeable policy (iOS-only).
12
+ * - `'foreground'` — granted while in use; foreground tracking only.
13
+ * - `'background'` — granted at all times; background tracking works.
14
+ */
15
+ export type PermissionLevel =
16
+ | 'notDetermined'
17
+ | 'denied'
18
+ | 'restricted'
19
+ | 'foreground'
20
+ | 'background';
21
+
22
+ /**
23
+ * Precision of a granted location authorization. `null` unless granted.
24
+ *
25
+ * - `'full'` — precise location.
26
+ * - `'reduced'` — approximate only (iOS 14 / Android 12 "approximate").
27
+ */
28
+ export type PermissionAccuracy = 'full' | 'reduced';
29
+
30
+ /**
31
+ * A point-in-time snapshot of the location-permission grant, returned by
32
+ * `Beekon.getPermissionStatus()`. Mirrors the native `PermissionStatus`.
33
+ *
34
+ * Beekon never *requests* permission — the app owns that. This is a read-only
35
+ * query for pre-start checks; it never prompts. It is a snapshot, not an
36
+ * observer: once tracking is running, permission loss surfaces on the `onState`
37
+ * stream as `{ kind: 'stopped', reason: 'permissionDenied' }`.
38
+ */
39
+ export type PermissionStatus = {
40
+ /** How far the grant reaches. */
41
+ level: PermissionLevel;
42
+ /** Precision of the grant, or `null` when not authorized. */
43
+ accuracy: PermissionAccuracy | null;
44
+ /** `true` when authorized at all — foreground or background. */
45
+ isAuthorized: boolean;
46
+ /** `true` when background tracking is authorized. */
47
+ canTrackInBackground: boolean;
48
+ };
@@ -8,6 +8,9 @@
8
8
  * (user-fixable in Settings).
9
9
  * - `'locationUnavailable'` — no usable location backend on this device (e.g.
10
10
  * Google Play Services absent/outdated on Android). Not user-fixable.
11
+ * - `'cloudModeUnavailable'` — the SDK was configured for cloud mode but the
12
+ * running build cannot operate it (cloud-mode-v1). Not user-fixable; switch to
13
+ * a self-managed config or a cloud-capable SDK build.
11
14
  * - `'system'` — the OS terminated tracking (e.g. foreground-service kill,
12
15
  * or an unrecoverable internal error).
13
16
  */
@@ -16,6 +19,7 @@ export type StopReason =
16
19
  | 'permissionDenied'
17
20
  | 'locationServicesDisabled'
18
21
  | 'locationUnavailable'
22
+ | 'cloudModeUnavailable'
19
23
  | 'system';
20
24
 
21
25
  /**