@wayq/beekon-rn 0.0.3 → 0.0.6

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 (84) hide show
  1. package/BeekonRn.podspec +5 -3
  2. package/CHANGELOG.md +103 -0
  3. package/README.md +326 -52
  4. package/android/build.gradle +9 -4
  5. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +411 -59
  6. package/ios/BeekonRn.mm +103 -24
  7. package/ios/BeekonRn.swift +465 -61
  8. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
  9. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  10. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +11424 -1279
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +262 -36
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +11424 -1279
  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 +262 -36
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +11424 -1279
  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 +262 -36
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +2 -80
  23. package/lib/module/NativeBeekonRn.js +35 -7
  24. package/lib/module/NativeBeekonRn.js.map +1 -1
  25. package/lib/module/beekon.js +246 -45
  26. package/lib/module/beekon.js.map +1 -1
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/internal/mappers.js +166 -25
  29. package/lib/module/internal/mappers.js.map +1 -1
  30. package/lib/module/types/auth.js +4 -0
  31. package/lib/module/types/auth.js.map +1 -0
  32. package/lib/module/types/config.js +2 -0
  33. package/lib/module/types/enums.js +2 -0
  34. package/lib/module/types/enums.js.map +1 -0
  35. package/lib/module/types/error.js +20 -4
  36. package/lib/module/types/error.js.map +1 -1
  37. package/lib/module/types/geofence.js +2 -0
  38. package/lib/module/types/geofence.js.map +1 -0
  39. package/lib/module/types/location.js +2 -0
  40. package/lib/module/types/sync.js +2 -0
  41. package/lib/module/types/sync.js.map +1 -0
  42. package/lib/typescript/src/NativeBeekonRn.d.ts +150 -20
  43. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  44. package/lib/typescript/src/beekon.d.ts +110 -33
  45. package/lib/typescript/src/beekon.d.ts.map +1 -1
  46. package/lib/typescript/src/index.d.ts +6 -2
  47. package/lib/typescript/src/index.d.ts.map +1 -1
  48. package/lib/typescript/src/internal/mappers.d.ts +16 -6
  49. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  50. package/lib/typescript/src/types/auth.d.ts +99 -0
  51. package/lib/typescript/src/types/auth.d.ts.map +1 -0
  52. package/lib/typescript/src/types/config.d.ts +66 -20
  53. package/lib/typescript/src/types/config.d.ts.map +1 -1
  54. package/lib/typescript/src/types/enums.d.ts +62 -0
  55. package/lib/typescript/src/types/enums.d.ts.map +1 -0
  56. package/lib/typescript/src/types/error.d.ts +21 -5
  57. package/lib/typescript/src/types/error.d.ts.map +1 -1
  58. package/lib/typescript/src/types/geofence.d.ts +36 -0
  59. package/lib/typescript/src/types/geofence.d.ts.map +1 -0
  60. package/lib/typescript/src/types/location.d.ts +22 -8
  61. package/lib/typescript/src/types/location.d.ts.map +1 -1
  62. package/lib/typescript/src/types/state.d.ts +13 -4
  63. package/lib/typescript/src/types/state.d.ts.map +1 -1
  64. package/lib/typescript/src/types/sync.d.ts +27 -0
  65. package/lib/typescript/src/types/sync.d.ts.map +1 -0
  66. package/package.json +8 -5
  67. package/scripts/fetch-beekonkit.sh +5 -5
  68. package/src/NativeBeekonRn.ts +165 -20
  69. package/src/beekon.ts +278 -48
  70. package/src/index.tsx +24 -2
  71. package/src/internal/mappers.ts +242 -27
  72. package/src/types/auth.ts +101 -0
  73. package/src/types/config.ts +68 -20
  74. package/src/types/enums.ts +80 -0
  75. package/src/types/error.ts +23 -5
  76. package/src/types/geofence.ts +37 -0
  77. package/src/types/location.ts +28 -8
  78. package/src/types/state.ts +13 -3
  79. package/src/types/sync.ts +23 -0
  80. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeDirectory +0 -0
  81. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements +0 -0
  82. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeResources +0 -296
  83. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeSignature +0 -0
  84. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/_CodeSignature/CodeResources +0 -146
@@ -1,43 +1,198 @@
1
+ import type { AuthConfig, AuthTokens } from '../types/auth';
1
2
  import type { BeekonConfig } from '../types/config';
2
- import type { BeekonState, StopReason } from '../types/state';
3
+ import type {
4
+ AccuracyMode,
5
+ ActivityType,
6
+ AuthBodyFormat,
7
+ AuthStrategy,
8
+ LocationQuality,
9
+ LocationTrigger,
10
+ MotionState,
11
+ StationaryMode,
12
+ } from '../types/enums';
13
+ import type {
14
+ BeekonGeofence,
15
+ GeofenceEvent,
16
+ Transition,
17
+ } from '../types/geofence';
3
18
  import type { Location } from '../types/location';
19
+ import type { BeekonState, StopReason } from '../types/state';
20
+ import type { SyncFailure, SyncStatus } from '../types/sync';
4
21
  import { BeekonError, type BeekonErrorKind } from '../types/error';
5
- import type { WireConfig, WireLocation, WireState } from '../NativeBeekonRn';
22
+ import type {
23
+ WireAuthConfig,
24
+ WireAuthTokens,
25
+ WireConfig,
26
+ WireGeofence,
27
+ WireGeofenceEvent,
28
+ WireKeyValue,
29
+ WireLocation,
30
+ WireState,
31
+ WireSyncStatus,
32
+ } from '../NativeBeekonRn';
33
+
34
+ // Config defaults. Kept here (not in the wire layer) so the native modules can
35
+ // treat every wire field as present.
36
+ const DEFAULTS = {
37
+ minTimeBetweenLocationsSeconds: 30,
38
+ minDistanceBetweenLocationsMeters: 100,
39
+ accuracyMode: 'balanced' as AccuracyMode,
40
+ whenStationary: 'pause' as StationaryMode,
41
+ stationaryRadiusMeters: 5,
42
+ detectActivity: false,
43
+ syncIntervalSeconds: 300,
44
+ syncBatchSize: 100,
45
+ authStrategy: 'bearer' as AuthStrategy,
46
+ authBodyFormat: 'form' as AuthBodyFormat,
47
+ authSkewMarginSeconds: 60,
48
+ authRefreshHeaders: { Authorization: 'Bearer {accessToken}' } as Record<
49
+ string,
50
+ string
51
+ >,
52
+ };
6
53
 
7
- const DEFAULT_INTERVAL_SECONDS = 30;
8
- const DEFAULT_DISTANCE_METERS = 100;
54
+ // --- Public → wire ---------------------------------------------------------
55
+
56
+ export function recordToEntries(
57
+ record: Record<string, string> | undefined
58
+ ): WireKeyValue[] {
59
+ if (!record) return [];
60
+ return Object.keys(record).map((key) => ({ key, value: record[key]! }));
61
+ }
9
62
 
10
63
  export function configToWire(config: BeekonConfig): WireConfig {
64
+ const sync = config.sync;
65
+ return {
66
+ minTimeBetweenLocationsSeconds:
67
+ config.minTimeBetweenLocationsSeconds ??
68
+ DEFAULTS.minTimeBetweenLocationsSeconds,
69
+ minDistanceBetweenLocationsMeters:
70
+ config.minDistanceBetweenLocationsMeters ??
71
+ DEFAULTS.minDistanceBetweenLocationsMeters,
72
+ accuracyMode: config.accuracyMode ?? DEFAULTS.accuracyMode,
73
+ whenStationary: config.whenStationary ?? DEFAULTS.whenStationary,
74
+ stationaryRadiusMeters:
75
+ config.stationaryRadiusMeters ?? DEFAULTS.stationaryRadiusMeters,
76
+ detectActivity: config.detectActivity ?? DEFAULTS.detectActivity,
77
+ sync: sync
78
+ ? {
79
+ url: sync.url,
80
+ headers: recordToEntries(sync.headers),
81
+ intervalSeconds: sync.intervalSeconds ?? DEFAULTS.syncIntervalSeconds,
82
+ batchSize: sync.batchSize ?? DEFAULTS.syncBatchSize,
83
+ auth: sync.auth ? authToWire(sync.auth) : undefined,
84
+ }
85
+ : undefined,
86
+ notification: config.notification
87
+ ? {
88
+ title: config.notification.title,
89
+ text: config.notification.text,
90
+ smallIcon: config.notification.smallIcon,
91
+ }
92
+ : undefined,
93
+ };
94
+ }
95
+
96
+ export function geofenceToWire(g: BeekonGeofence): WireGeofence {
97
+ return {
98
+ id: g.id,
99
+ lat: g.latitude,
100
+ lng: g.longitude,
101
+ radiusMeters: g.radiusMeters,
102
+ notifyOnEntry: g.notifyOnEntry ?? true,
103
+ notifyOnExit: g.notifyOnExit ?? true,
104
+ };
105
+ }
106
+
107
+ // `expiresAt` (a `Date`) becomes epoch millis on the wire; the native side
108
+ // converts to epoch seconds. String maps travel as `WireKeyValue[]`.
109
+ export function authToWire(a: AuthConfig): WireAuthConfig {
11
110
  return {
12
- intervalSeconds: config.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS,
13
- distanceMeters: config.distanceMeters ?? DEFAULT_DISTANCE_METERS,
14
- androidNotification: config.androidNotification,
111
+ accessToken: a.accessToken,
112
+ refreshToken: a.refreshToken,
113
+ expiresAtMs: a.expiresAt?.getTime(),
114
+ strategy: a.strategy ?? DEFAULTS.authStrategy,
115
+ refreshUrl: a.refreshUrl,
116
+ refreshPayload: recordToEntries(a.refreshPayload),
117
+ refreshHeaders: recordToEntries(
118
+ a.refreshHeaders ?? DEFAULTS.authRefreshHeaders
119
+ ),
120
+ refreshBodyFormat: a.refreshBodyFormat ?? DEFAULTS.authBodyFormat,
121
+ responseMapping: {
122
+ accessToken: a.responseMapping?.accessToken,
123
+ refreshToken: a.responseMapping?.refreshToken,
124
+ expiresIn: a.responseMapping?.expiresIn,
125
+ expiresAt: a.responseMapping?.expiresAt,
126
+ },
127
+ skewMarginSeconds: a.skewMarginSeconds ?? DEFAULTS.authSkewMarginSeconds,
128
+ seedEpoch: a.seedEpoch,
15
129
  };
16
130
  }
17
131
 
132
+ // --- Wire → public ---------------------------------------------------------
133
+
18
134
  export function wireToLocation(w: WireLocation): Location {
19
135
  return {
20
- lat: w.lat,
21
- lng: w.lng,
136
+ id: w.id,
137
+ latitude: w.lat,
138
+ longitude: w.lng,
139
+ timestamp: new Date(w.timestampMs),
22
140
  accuracy: w.accuracy,
23
141
  speed: w.speed,
24
142
  bearing: w.bearing,
25
143
  altitude: w.altitude,
26
- timestamp: new Date(w.timestampMs),
144
+ quality: oneOf<LocationQuality>(
145
+ w.quality,
146
+ ['ok', 'lowAccuracy', 'implausibleSpeed'],
147
+ 'ok'
148
+ ),
149
+ trigger: oneOf<LocationTrigger>(
150
+ w.trigger,
151
+ ['interval', 'motion', 'checkIn', 'geofence', 'manual'],
152
+ 'interval'
153
+ ),
154
+ motion: oneOf<MotionState>(
155
+ w.motion,
156
+ ['moving', 'stationary', 'unknown'],
157
+ 'unknown'
158
+ ),
159
+ activity:
160
+ w.activity == null
161
+ ? null
162
+ : oneOf<ActivityType>(
163
+ w.activity,
164
+ [
165
+ 'stationary',
166
+ 'walking',
167
+ 'running',
168
+ 'cycling',
169
+ 'automotive',
170
+ 'unknown',
171
+ ],
172
+ 'unknown'
173
+ ),
174
+ isMock: w.isMock,
27
175
  };
28
176
  }
29
177
 
30
- function toStopReason(s: string | undefined): StopReason {
31
- switch (s) {
32
- case 'user':
33
- case 'permissionDenied':
34
- case 'locationServicesDisabled':
35
- case 'system':
36
- return s;
37
- default:
38
- // Defensive — native should never emit an unknown reason.
39
- return 'user';
40
- }
178
+ export function wireToGeofence(w: WireGeofence): BeekonGeofence {
179
+ return {
180
+ id: w.id,
181
+ latitude: w.lat,
182
+ longitude: w.lng,
183
+ radiusMeters: w.radiusMeters,
184
+ notifyOnEntry: w.notifyOnEntry,
185
+ notifyOnExit: w.notifyOnExit,
186
+ };
187
+ }
188
+
189
+ export function wireToGeofenceEvent(w: WireGeofenceEvent): GeofenceEvent {
190
+ return {
191
+ id: w.id,
192
+ geofenceId: w.geofenceId,
193
+ type: oneOf<Transition>(w.type, ['enter', 'exit'], 'enter'),
194
+ timestamp: new Date(w.timestampMs),
195
+ };
41
196
  }
42
197
 
43
198
  export function wireToState(w: WireState): BeekonState {
@@ -54,11 +209,67 @@ export function wireToState(w: WireState): BeekonState {
54
209
  }
55
210
  }
56
211
 
212
+ export function wireToSyncStatus(w: WireSyncStatus): SyncStatus {
213
+ switch (w.type) {
214
+ case 'idle':
215
+ return { kind: 'idle' };
216
+ case 'pending':
217
+ return { kind: 'pending' };
218
+ case 'failed':
219
+ return { kind: 'failed', reason: toSyncFailure(w.failure) };
220
+ default:
221
+ return { kind: 'idle' };
222
+ }
223
+ }
224
+
225
+ /** `expiresAtMs` (epoch millis) becomes a `Date`; `null` propagates faithfully. */
226
+ export function wireToAuthTokens(w: WireAuthTokens): AuthTokens {
227
+ return {
228
+ accessToken: w.accessToken,
229
+ refreshToken: w.refreshToken,
230
+ expiresAt: w.expiresAtMs == null ? null : new Date(w.expiresAtMs),
231
+ epoch: w.epoch,
232
+ };
233
+ }
234
+
235
+ function toStopReason(s: string | undefined): StopReason {
236
+ return oneOf<StopReason>(
237
+ s,
238
+ [
239
+ 'user',
240
+ 'permissionDenied',
241
+ 'locationServicesDisabled',
242
+ 'locationUnavailable',
243
+ 'system',
244
+ ],
245
+ // The native side always populates the reason for a `stopped` state; an
246
+ // unknown/missing value means the wire contract was violated — fall back to
247
+ // `system` rather than throwing on a subscriber.
248
+ 'system'
249
+ );
250
+ }
251
+
252
+ function toSyncFailure(s: string | undefined): SyncFailure {
253
+ return oneOf<SyncFailure>(s, ['auth', 'rejected'], 'rejected');
254
+ }
255
+
256
+ /** Validate a wire enum string against the known set, with a safe fallback. */
257
+ function oneOf<T extends string>(
258
+ value: string | null | undefined,
259
+ allowed: readonly T[],
260
+ fallback: T
261
+ ): T {
262
+ return (allowed as readonly string[]).includes(value ?? '')
263
+ ? (value as T)
264
+ : fallback;
265
+ }
266
+
267
+ // --- Errors ----------------------------------------------------------------
268
+
57
269
  /**
58
- * Re-throw a native promise rejection as a typed `BeekonError`. Native modules
59
- * encode the kind in the error code string (`PERMISSION_DENIED`,
60
- * `LOCATION_SERVICES_DISABLED`, `STORAGE_FAILURE`); anything else falls
61
- * through as the original error.
270
+ * Re-throw a native promise rejection as a typed {@link BeekonError}. The native
271
+ * modules encode the kind in the error-code string (`STORAGE_FAILURE`,
272
+ * `INVALID_GEOFENCE`); anything else falls through as the original error.
62
273
  */
63
274
  export function rethrowAsBeekonError(e: unknown): never {
64
275
  const code = (e as { code?: string } | undefined)?.code;
@@ -72,12 +283,16 @@ export function rethrowAsBeekonError(e: unknown): never {
72
283
 
73
284
  function codeToKind(code: string | undefined): BeekonErrorKind | undefined {
74
285
  switch (code) {
286
+ case 'STORAGE_FAILURE':
287
+ return 'storage';
288
+ case 'INVALID_GEOFENCE':
289
+ return 'invalidGeofence';
75
290
  case 'PERMISSION_DENIED':
76
291
  return 'permissionDenied';
77
292
  case 'LOCATION_SERVICES_DISABLED':
78
293
  return 'locationServicesDisabled';
79
- case 'STORAGE_FAILURE':
80
- return 'storageFailure';
294
+ case 'LOCATION_UNAVAILABLE':
295
+ return 'locationUnavailable';
81
296
  default:
82
297
  return undefined;
83
298
  }
@@ -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
+ };
@@ -1,33 +1,81 @@
1
+ import type { AuthConfig } from './auth';
2
+ import type { AccuracyMode, StationaryMode } from './enums';
3
+
1
4
  /**
2
- * Foreground-service notification overrides. Android-only — required by the
3
- * platform because every location foreground service must show a persistent
4
- * notification. All fields are optional; missing values resolve at runtime
5
- * against the host app's label and icon. Ignored on iOS.
5
+ * Foreground-service notification overrides. **Android-only**the platform
6
+ * requires every location foreground service to show a persistent notification.
7
+ * Both fields are optional; missing values fall back to native defaults.
8
+ * Ignored on iOS.
6
9
  */
7
- export type AndroidNotificationConfig = {
8
- /** Notification title. Defaults to "Location active". */
10
+ export type NotificationConfig = {
11
+ /** Notification title. Defaults to "Tracking location". */
9
12
  title?: string;
10
- /** Notification body text. Defaults to the host app's label. */
13
+ /** Notification body text. Defaults to no body. */
11
14
  text?: string;
12
15
  /**
13
- * Drawable resource name (e.g. `ic_notification`) resolved at runtime via
14
- * `Resources.getIdentifier()` against the host app's `res/drawable*` folders.
15
- * Pass the resource name without extension or `R.drawable.` prefix. Defaults
16
- * to the host app's `applicationInfo.icon`.
16
+ * Status-bar icon, given as an Android drawable or mipmap resource **name**
17
+ * `"ic_notification"`, `"drawable/ic_x"`, or `"mipmap/ic_x"`. Android
18
+ * renders the small icon from its alpha channel only, so supply a monochrome
19
+ * silhouette on a transparent background. Falls back to the host app's
20
+ * launcher icon when omitted or unresolvable.
17
21
  */
18
- smallIconResName?: string;
22
+ smallIcon?: string;
19
23
  };
20
24
 
21
25
  /**
22
- * Tracking configuration. Two knobs: a minimum time interval and a minimum
23
- * distance between emitted fixes. A fix is admitted only when **both** gates
24
- * are satisfied. Pass `0` to disable a gate.
26
+ * Server-upload configuration. Presence of {@link BeekonConfig.sync} turns on
27
+ * batched uploads of stored locations and geofence events; omitting it keeps
28
+ * tracking local-only.
25
29
  */
26
- export type BeekonConfig = {
27
- /** Minimum seconds between admitted fixes. Default: `30`. `0` disables the time gate. */
30
+ export type SyncConfig = {
31
+ /** Ingest endpoint an `http` or `https` URL. */
32
+ url: string;
33
+ /** Extra HTTP headers sent with every upload (e.g. `Authorization`). */
34
+ headers?: Record<string, string>;
35
+ /**
36
+ * Target seconds between uploads. The SDK enforces a 60s floor; background
37
+ * uploads may be delayed to the OS scheduler floor (~15 min). Default: `300`.
38
+ */
28
39
  intervalSeconds?: number;
29
- /** Minimum metres between admitted fixes. Default: `100`. `0` disables the distance gate. */
30
- distanceMeters?: number;
40
+ /** Maximum locations per upload. Clamped to `[1, 1000]`. Default: `100`. */
41
+ batchSize?: number;
42
+ /**
43
+ * Declarative token-refresh recipe. When set, the SDK attaches and natively
44
+ * refreshes the access token (proactively before expiry, reactively on
45
+ * `401`/`403`). Omit to keep the legacy static-{@link SyncConfig.headers}
46
+ * behaviour. Requires the native SDK ≥ 0.0.6. See {@link AuthConfig}.
47
+ */
48
+ auth?: AuthConfig;
49
+ };
50
+
51
+ /**
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.
55
+ *
56
+ * The two gate knobs (`minTimeBetweenLocationsSeconds`,
57
+ * `minDistanceBetweenLocationsMeters`) admit a fix only when **both** are
58
+ * satisfied; pass `0` to disable a gate.
59
+ */
60
+ export type BeekonConfig = {
61
+ /** Minimum seconds between admitted fixes (10s floor). Default: `30`. */
62
+ minTimeBetweenLocationsSeconds?: number;
63
+ /** Minimum metres between admitted fixes. `0` disables it. Default: `100`. */
64
+ minDistanceBetweenLocationsMeters?: number;
65
+ /** Accuracy / battery preset. Default: `'balanced'`. */
66
+ accuracyMode?: AccuracyMode;
67
+ /** Behaviour while stationary. Default: `'pause'`. */
68
+ whenStationary?: StationaryMode;
69
+ /** Stationary exit-geofence radius, clamped to `[5, 1000]`. Default: `5`. */
70
+ stationaryRadiusMeters?: number;
71
+ /**
72
+ * Detect physical activity (walking, driving, …). Requires the
73
+ * `ACTIVITY_RECOGNITION` permission on Android and the `NSMotionUsageDescription`
74
+ * Info.plist key on iOS. Default: `false`.
75
+ */
76
+ detectActivity?: boolean;
77
+ /** Server-upload configuration. Omit to keep tracking local-only. */
78
+ sync?: SyncConfig;
31
79
  /** Android-only notification overrides. Ignored on iOS. */
32
- androidNotification?: AndroidNotificationConfig;
80
+ notification?: NotificationConfig;
33
81
  };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Value enums shared across the public API, expressed as string-literal unions
3
+ * (idiomatic TS). Each mirrors the like-named enum on both native SDKs by value;
4
+ * the wire layer carries them as plain strings and the mappers translate.
5
+ */
6
+
7
+ /**
8
+ * Accuracy / battery-life preset for the location provider.
9
+ *
10
+ * - `'high'` — highest precision, highest battery cost (GPS-grade).
11
+ * - `'balanced'` — city-block precision, balanced battery (the default).
12
+ * - `'low'` — coarse, lowest battery.
13
+ */
14
+ export type AccuracyMode = 'high' | 'balanced' | 'low';
15
+
16
+ /**
17
+ * Behaviour while the device is stationary.
18
+ *
19
+ * - `'keepTracking'` — GPS stays on.
20
+ * - `'pause'` — GPS off while stationary, silent resume on movement (default).
21
+ * - `'pauseWithCheckIns'` — GPS off, but a location is recorded roughly every
22
+ * 15 minutes.
23
+ */
24
+ export type StationaryMode = 'keepTracking' | 'pause' | 'pauseWithCheckIns';
25
+
26
+ /**
27
+ * Why a {@link Location} was admitted and persisted.
28
+ *
29
+ * - `'interval'` — a scheduled fix that passed the time/distance gate.
30
+ * - `'motion'` — the device started or stopped moving.
31
+ * - `'checkIn'` — a periodic check-in while stationary.
32
+ * - `'geofence'` — a geofence crossing.
33
+ * - `'manual'` — an explicit request.
34
+ */
35
+ export type LocationTrigger =
36
+ | 'interval'
37
+ | 'motion'
38
+ | 'checkIn'
39
+ | 'geofence'
40
+ | 'manual';
41
+
42
+ /**
43
+ * The gate's quality verdict for a {@link Location}.
44
+ *
45
+ * - `'ok'` — passed the accuracy and speed checks.
46
+ * - `'lowAccuracy'` — worse than the usable accuracy threshold.
47
+ * - `'implausibleSpeed'` — an implausible jump from the previous fix.
48
+ */
49
+ export type LocationQuality = 'ok' | 'lowAccuracy' | 'implausibleSpeed';
50
+
51
+ /** Device motion when a {@link Location} was captured. */
52
+ export type MotionState = 'moving' | 'stationary' | 'unknown';
53
+
54
+ /**
55
+ * Detected physical activity. Only populated when
56
+ * {@link BeekonConfig.detectActivity} is enabled; otherwise `null`.
57
+ */
58
+ export type ActivityType =
59
+ | 'stationary'
60
+ | 'walking'
61
+ | 'running'
62
+ | 'cycling'
63
+ | 'automotive'
64
+ | 'unknown';
65
+
66
+ /**
67
+ * How the access token is attached to upload requests (token refresh).
68
+ *
69
+ * - `'bearer'` — `Authorization: Bearer <accessToken>` (JWT / OAuth2 style, default).
70
+ * - `'raw'` — `Authorization: <accessToken>` (the raw token, no scheme).
71
+ */
72
+ export type AuthStrategy = 'bearer' | 'raw';
73
+
74
+ /**
75
+ * Encoding of the token-refresh request body.
76
+ *
77
+ * - `'form'` — `application/x-www-form-urlencoded` (default; most OAuth2 endpoints).
78
+ * - `'json'` — `application/json`.
79
+ */
80
+ export type AuthBodyFormat = 'form' | 'json';
@@ -1,15 +1,33 @@
1
1
  /**
2
- * Error kinds thrown from `Beekon.start()` / `Beekon.history()`. Match the
3
- * native `BeekonError` cases byte-for-byte.
2
+ * Error kinds thrown across the bridge.
3
+ *
4
+ * Lifecycle problems on the tracking path never throw — they surface on the
5
+ * `onState` stream as `stopped(reason)`. The exceptions are the storage/geofence
6
+ * kinds below and the precondition kinds thrown by the explicit one-shot
7
+ * `getCurrentLocation()` (which has no `onState` arc of its own to report on; a
8
+ * plain timeout returns `null` instead of throwing).
9
+ *
10
+ * - `'storage'` — a local-store (SQLite) read/write failed. Thrown by
11
+ * `getLocations()`, `deleteLocations()`, and `pendingUploadCount()`.
12
+ * - `'invalidGeofence'` — a geofence passed to `addGeofences()` failed
13
+ * validation (empty/oversized id, or non-positive radius).
14
+ * - `'permissionDenied'` — location permission is missing. Thrown by
15
+ * `getCurrentLocation()`.
16
+ * - `'locationServicesDisabled'` — system location services are off. Thrown by
17
+ * `getCurrentLocation()`.
18
+ * - `'locationUnavailable'` — location is otherwise unavailable (e.g. Play
19
+ * Services absent/old). Thrown by `getCurrentLocation()`.
4
20
  */
5
21
  export type BeekonErrorKind =
22
+ | 'storage'
23
+ | 'invalidGeofence'
6
24
  | 'permissionDenied'
7
25
  | 'locationServicesDisabled'
8
- | 'storageFailure';
26
+ | 'locationUnavailable';
9
27
 
10
28
  /**
11
- * Typed error surfaced from the bridge. The native module rejects promises
12
- * with the kind encoded as the error code; the facade re-wraps into this.
29
+ * Typed error surfaced from the bridge. The native module rejects promises with
30
+ * the kind encoded as the error code; the facade re-wraps into this.
13
31
  */
14
32
  export class BeekonError extends Error {
15
33
  public readonly kind: BeekonErrorKind;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A circular geofence registered via `Beekon.addGeofences()`. Mirrors
3
+ * `BeekonGeofence` on both native SDKs. The SDK pages registrations
4
+ * automatically, so you may register more than the OS geofence limit.
5
+ */
6
+ export type BeekonGeofence = {
7
+ /** Unique id, 1–100 characters. Re-adding an existing id replaces it. */
8
+ id: string;
9
+ /** Center latitude in degrees, WGS-84. */
10
+ latitude: number;
11
+ /** Center longitude in degrees, WGS-84. */
12
+ longitude: number;
13
+ /** Radius in metres. Must be `> 0`; `>= 100` is recommended for reliability. */
14
+ radiusMeters: number;
15
+ /** Emit an event on entry. Default: `true`. */
16
+ notifyOnEntry?: boolean;
17
+ /** Emit an event on exit. Default: `true`. */
18
+ notifyOnExit?: boolean;
19
+ };
20
+
21
+ /** Whether a {@link GeofenceEvent} marks entering or exiting a geofence. */
22
+ export type Transition = 'enter' | 'exit';
23
+
24
+ /**
25
+ * A geofence entry/exit crossing, delivered via `Beekon.onGeofenceEvent()`.
26
+ * Mirrors `GeofenceEvent` on both native SDKs.
27
+ */
28
+ export type GeofenceEvent = {
29
+ /** Unique event id. Used as the dedup key on upload. */
30
+ id: string;
31
+ /** The {@link BeekonGeofence.id} that was crossed. */
32
+ geofenceId: string;
33
+ /** Whether the device entered or exited. */
34
+ type: Transition;
35
+ /** When the crossing happened. */
36
+ timestamp: Date;
37
+ };