@wayq/beekon-rn 0.0.5 → 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 (59) hide show
  1. package/BeekonRn.podspec +4 -2
  2. package/CHANGELOG.md +103 -0
  3. package/README.md +303 -81
  4. package/android/build.gradle +1 -1
  5. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +113 -7
  6. package/ios/BeekonRn.mm +13 -0
  7. package/ios/BeekonRn.swift +113 -9
  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 +5399 -1463
  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 +77 -2
  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 +5399 -1463
  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 +77 -2
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5399 -1463
  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 +77 -2
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
  23. package/lib/module/NativeBeekonRn.js +13 -0
  24. package/lib/module/NativeBeekonRn.js.map +1 -1
  25. package/lib/module/beekon.js +56 -7
  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 +50 -3
  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/error.js +13 -3
  33. package/lib/module/types/error.js.map +1 -1
  34. package/lib/typescript/src/NativeBeekonRn.d.ts +48 -0
  35. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  36. package/lib/typescript/src/beekon.d.ts +30 -1
  37. package/lib/typescript/src/beekon.d.ts.map +1 -1
  38. package/lib/typescript/src/index.d.ts +2 -1
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/internal/mappers.d.ts +5 -1
  41. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  42. package/lib/typescript/src/types/auth.d.ts +99 -0
  43. package/lib/typescript/src/types/auth.d.ts.map +1 -0
  44. package/lib/typescript/src/types/config.d.ts +16 -0
  45. package/lib/typescript/src/types/config.d.ts.map +1 -1
  46. package/lib/typescript/src/types/enums.d.ts +14 -0
  47. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  48. package/lib/typescript/src/types/error.d.ts +14 -4
  49. package/lib/typescript/src/types/error.d.ts.map +1 -1
  50. package/package.json +5 -1
  51. package/scripts/fetch-beekonkit.sh +4 -4
  52. package/src/NativeBeekonRn.ts +55 -0
  53. package/src/beekon.ts +66 -6
  54. package/src/index.tsx +3 -0
  55. package/src/internal/mappers.ts +59 -1
  56. package/src/types/auth.ts +101 -0
  57. package/src/types/config.ts +16 -0
  58. package/src/types/enums.ts +16 -0
  59. package/src/types/error.ts +19 -4
@@ -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,
@@ -17,6 +20,8 @@ import type { BeekonState, StopReason } from '../types/state';
17
20
  import type { SyncFailure, SyncStatus } from '../types/sync';
18
21
  import { BeekonError, type BeekonErrorKind } from '../types/error';
19
22
  import type {
23
+ WireAuthConfig,
24
+ WireAuthTokens,
20
25
  WireConfig,
21
26
  WireGeofence,
22
27
  WireGeofenceEvent,
@@ -37,6 +42,13 @@ const DEFAULTS = {
37
42
  detectActivity: false,
38
43
  syncIntervalSeconds: 300,
39
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
+ >,
40
52
  };
41
53
 
42
54
  // --- Public → wire ---------------------------------------------------------
@@ -68,10 +80,15 @@ export function configToWire(config: BeekonConfig): WireConfig {
68
80
  headers: recordToEntries(sync.headers),
69
81
  intervalSeconds: sync.intervalSeconds ?? DEFAULTS.syncIntervalSeconds,
70
82
  batchSize: sync.batchSize ?? DEFAULTS.syncBatchSize,
83
+ auth: sync.auth ? authToWire(sync.auth) : undefined,
71
84
  }
72
85
  : undefined,
73
86
  notification: config.notification
74
- ? { title: config.notification.title, text: config.notification.text }
87
+ ? {
88
+ title: config.notification.title,
89
+ text: config.notification.text,
90
+ smallIcon: config.notification.smallIcon,
91
+ }
75
92
  : undefined,
76
93
  };
77
94
  }
@@ -87,6 +104,31 @@ export function geofenceToWire(g: BeekonGeofence): WireGeofence {
87
104
  };
88
105
  }
89
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 {
110
+ return {
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,
129
+ };
130
+ }
131
+
90
132
  // --- Wire → public ---------------------------------------------------------
91
133
 
92
134
  export function wireToLocation(w: WireLocation): Location {
@@ -180,6 +222,16 @@ export function wireToSyncStatus(w: WireSyncStatus): SyncStatus {
180
222
  }
181
223
  }
182
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
+
183
235
  function toStopReason(s: string | undefined): StopReason {
184
236
  return oneOf<StopReason>(
185
237
  s,
@@ -235,6 +287,12 @@ function codeToKind(code: string | undefined): BeekonErrorKind | undefined {
235
287
  return 'storage';
236
288
  case 'INVALID_GEOFENCE':
237
289
  return 'invalidGeofence';
290
+ case 'PERMISSION_DENIED':
291
+ return 'permissionDenied';
292
+ case 'LOCATION_SERVICES_DISABLED':
293
+ return 'locationServicesDisabled';
294
+ case 'LOCATION_UNAVAILABLE':
295
+ return 'locationUnavailable';
238
296
  default:
239
297
  return undefined;
240
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,3 +1,4 @@
1
+ import type { AuthConfig } from './auth';
1
2
  import type { AccuracyMode, StationaryMode } from './enums';
2
3
 
3
4
  /**
@@ -11,6 +12,14 @@ export type NotificationConfig = {
11
12
  title?: string;
12
13
  /** Notification body text. Defaults to no body. */
13
14
  text?: string;
15
+ /**
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.
21
+ */
22
+ smallIcon?: string;
14
23
  };
15
24
 
16
25
  /**
@@ -30,6 +39,13 @@ export type SyncConfig = {
30
39
  intervalSeconds?: number;
31
40
  /** Maximum locations per upload. Clamped to `[1, 1000]`. Default: `100`. */
32
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;
33
49
  };
34
50
 
35
51
  /**
@@ -62,3 +62,19 @@ export type ActivityType =
62
62
  | 'cycling'
63
63
  | 'automotive'
64
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,14 +1,29 @@
1
1
  /**
2
- * Error kinds thrown across the bridge. These are the **only** errors Beekon
3
- * throws — permission / location-services / lifecycle problems never throw; they
4
- * surface on the `onState` stream as `stopped(reason)` instead.
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).
5
9
  *
6
10
  * - `'storage'` — a local-store (SQLite) read/write failed. Thrown by
7
11
  * `getLocations()`, `deleteLocations()`, and `pendingUploadCount()`.
8
12
  * - `'invalidGeofence'` — a geofence passed to `addGeofences()` failed
9
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()`.
10
20
  */
11
- export type BeekonErrorKind = 'storage' | 'invalidGeofence';
21
+ export type BeekonErrorKind =
22
+ | 'storage'
23
+ | 'invalidGeofence'
24
+ | 'permissionDenied'
25
+ | 'locationServicesDisabled'
26
+ | 'locationUnavailable';
12
27
 
13
28
  /**
14
29
  * Typed error surfaced from the bridge. The native module rejects promises with