@wayq/beekon-rn 0.0.7 → 0.0.9

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 (63) hide show
  1. package/BeekonRn.podspec +1 -1
  2. package/CHANGELOG.md +43 -0
  3. package/README.md +67 -5
  4. package/android/build.gradle +3 -2
  5. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +106 -8
  6. package/ios/BeekonRn.mm +35 -0
  7. package/ios/BeekonRn.swift +140 -8
  8. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  9. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  10. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +6048 -3656
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +95 -18
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +6048 -3656
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +95 -18
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +6048 -3656
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +95 -18
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
  22. package/lib/module/NativeBeekonRn.js.map +1 -1
  23. package/lib/module/beekon.js +97 -1
  24. package/lib/module/beekon.js.map +1 -1
  25. package/lib/module/index.js +4 -0
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/internal/licenseNudge.js +64 -0
  28. package/lib/module/internal/licenseNudge.js.map +1 -0
  29. package/lib/module/internal/mappers.js +56 -8
  30. package/lib/module/internal/mappers.js.map +1 -1
  31. package/lib/module/types/config.js +73 -1
  32. package/lib/module/types/config.js.map +1 -1
  33. package/lib/module/types/log.js +4 -0
  34. package/lib/module/types/log.js.map +1 -0
  35. package/lib/typescript/src/NativeBeekonRn.d.ts +47 -2
  36. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  37. package/lib/typescript/src/beekon.d.ts +46 -1
  38. package/lib/typescript/src/beekon.d.ts.map +1 -1
  39. package/lib/typescript/src/index.d.ts +4 -2
  40. package/lib/typescript/src/index.d.ts.map +1 -1
  41. package/lib/typescript/src/internal/licenseNudge.d.ts +38 -0
  42. package/lib/typescript/src/internal/licenseNudge.d.ts.map +1 -0
  43. package/lib/typescript/src/internal/mappers.d.ts +3 -1
  44. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  45. package/lib/typescript/src/types/config.d.ts +90 -7
  46. package/lib/typescript/src/types/config.d.ts.map +1 -1
  47. package/lib/typescript/src/types/enums.d.ts +8 -0
  48. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  49. package/lib/typescript/src/types/log.d.ts +22 -0
  50. package/lib/typescript/src/types/log.d.ts.map +1 -0
  51. package/lib/typescript/src/types/state.d.ts +4 -1
  52. package/lib/typescript/src/types/state.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/scripts/fetch-beekonkit.sh +4 -4
  55. package/src/NativeBeekonRn.ts +49 -2
  56. package/src/beekon.ts +100 -1
  57. package/src/index.tsx +7 -1
  58. package/src/internal/licenseNudge.ts +80 -0
  59. package/src/internal/mappers.ts +67 -7
  60. package/src/types/config.ts +99 -7
  61. package/src/types/enums.ts +9 -0
  62. package/src/types/log.ts +22 -0
  63. package/src/types/state.ts +4 -0
@@ -0,0 +1,22 @@
1
+ import type { LogLevel } from './enums';
2
+ /**
3
+ * A single diagnostic log record from the Beekon SDK.
4
+ *
5
+ * Delivered live on {@link Beekon.onLog} and read back from the persisted ring
6
+ * buffer via {@link Beekon.getLog}. Entries survive process death — the point of
7
+ * the buffer is post-hoc field diagnosis. Mirrors the native `LogEntry`; see
8
+ * `spec/diagnostics/log-format-v1`.
9
+ */
10
+ export type LogEntry = {
11
+ /** UUIDv7 — orders and dedups entries. */
12
+ id: string;
13
+ /** When the entry was recorded. */
14
+ timestamp: Date;
15
+ /** Severity. Always one of `'error'`…`'verbose'`. */
16
+ level: LogLevel;
17
+ /** Originating subsystem (e.g. `'location'`, `'sync'`); host breadcrumbs use `'app'`. */
18
+ category: string;
19
+ /** Human-readable, already-redacted text. */
20
+ message: string;
21
+ };
22
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../../../src/types/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;IAChB,qDAAqD;IACrD,KAAK,EAAE,QAAQ,CAAC;IAChB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
@@ -8,10 +8,13 @@
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
  */
14
- export type StopReason = 'user' | 'permissionDenied' | 'locationServicesDisabled' | 'locationUnavailable' | 'system';
17
+ export type StopReason = 'user' | 'permissionDenied' | 'locationServicesDisabled' | 'locationUnavailable' | 'cloudModeUnavailable' | 'system';
15
18
  /**
16
19
  * Tracking state. Mirrors the native `BeekonState` sealed type on both
17
20
  * platforms. Use the `kind` field as a discriminator.
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/types/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,GACrB,QAAQ,CAAC;AAEb;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/types/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,GACrB,sBAAsB,GACtB,QAAQ,CAAC;AAEb;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wayq/beekon-rn",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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",
@@ -13,12 +13,12 @@
13
13
 
14
14
  set -euo pipefail
15
15
 
16
- VERSION="0.0.7"
16
+ VERSION="0.0.9"
17
17
  URL="https://github.com/wayqteam/beekon-ios-binary/releases/download/v${VERSION}/BeekonKit.xcframework.zip"
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
18
+ # SHA256 of the v0.0.9 BeekonKit.xcframework.zip. Matches the SwiftPM
19
+ # `binaryTarget` checksum in beekon-ios-binary's Package.swift at tag v0.0.9
20
20
  # (SwiftPM's compute-checksum is the SHA256 of the zip).
21
- EXPECTED_SHA="48803b540f063f609230f44a0f5b7faca125847db9f4628d0bf5d31d351a762b"
21
+ EXPECTED_SHA="6f5e75bd90ef67c2e6466336edce4c3a33b5d44aa592fb5a6fa2b46eeab005e7"
22
22
 
23
23
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
24
24
  DEST_DIR="${ROOT}/ios/Frameworks"
@@ -59,6 +59,7 @@ export type WireSyncConfig = {
59
59
  headers: WireKeyValue[];
60
60
  intervalSeconds: number;
61
61
  batchSize: number;
62
+ syncThreshold: number;
62
63
  /** Token-refresh recipe; omitted keeps static-header auth. */
63
64
  auth?: WireAuthConfig;
64
65
  };
@@ -71,7 +72,27 @@ export type WireNotificationConfig = {
71
72
  };
72
73
 
73
74
  export type WireConfig = {
74
- /** All fields required at the wire level — the TS facade applies defaults. */
75
+ /**
76
+ * Configuration-ownership discriminator (cloud-mode-v1 §2). One of: 'cloud' |
77
+ * 'selfManaged'. The native side switches on this to build the matching arm of
78
+ * the sealed `BeekonConfig`.
79
+ */
80
+ mode: string;
81
+ /**
82
+ * Cloud arm only: the opaque project credential (`bkproj_…`). Omitted/empty for
83
+ * the self-managed arm; the native cloud arm trims it and rejects an empty key.
84
+ */
85
+ projectKey?: string;
86
+ /**
87
+ * Cloud arm only: base-URL override. Omitted means the native baked-in default
88
+ * (`https://api.getbeekon.com`). Ignored by the self-managed arm.
89
+ */
90
+ endpoint?: string;
91
+ /**
92
+ * Self-managed arm only (ignored by the cloud arm, which derives tracking
93
+ * config from the server). All fields required at the wire level — the TS
94
+ * facade applies defaults.
95
+ */
75
96
  minTimeBetweenLocationsSeconds: number;
76
97
  minDistanceBetweenLocationsMeters: number;
77
98
  /** One of: 'high' | 'balanced' | 'low'. */
@@ -91,6 +112,20 @@ export type WireConfig = {
91
112
  * object, so this field documents the shape rather than altering codegen.
92
113
  */
93
114
  licenseKey?: string;
115
+ /**
116
+ * Diagnostic log verbosity threshold (spec `diagnostics/log-format-v1` §1).
117
+ * One of: 'off' | 'error' | 'warn' | 'info' | 'debug' | 'verbose'.
118
+ */
119
+ logLevel: string;
120
+ };
121
+
122
+ export type WireLogEntry = {
123
+ id: string;
124
+ timestampMs: number;
125
+ /** One of: 'error' | 'warn' | 'info' | 'debug' | 'verbose'. */
126
+ level: string;
127
+ category: string;
128
+ message: string;
94
129
  };
95
130
 
96
131
  export type WireLocation = {
@@ -137,7 +172,7 @@ export type WireState = {
137
172
  /**
138
173
  * Only populated when `type === 'stopped'`. One of: 'user' |
139
174
  * 'permissionDenied' | 'locationServicesDisabled' | 'locationUnavailable' |
140
- * 'system'.
175
+ * 'cloudModeUnavailable' | 'system'.
141
176
  */
142
177
  stopReason?: string;
143
178
  };
@@ -232,12 +267,24 @@ export interface Spec extends TurboModule {
232
267
  */
233
268
  licenseStatus(): Promise<WireLicenseStatus>;
234
269
 
270
+ /** Persisted diagnostic log entries in `[fromMs, toMs]`, oldest first. */
271
+ getLog(fromMs: number, toMs: number): Promise<WireLogEntry[]>;
272
+ /** Serialize the whole log buffer to an NDJSON file; resolves its path. */
273
+ exportLog(): Promise<string>;
274
+ /** Delete all persisted diagnostic log entries. */
275
+ clearLog(): Promise<void>;
276
+ /** Set the diagnostic log threshold at runtime. */
277
+ setLogLevel(level: string): Promise<void>;
278
+ /** Write a host-app breadcrumb (category `app`) into the log pipeline. */
279
+ log(level: string, message: string): Promise<void>;
280
+
235
281
  readonly onState: CodegenTypes.EventEmitter<WireState>;
236
282
  readonly onLocation: CodegenTypes.EventEmitter<WireLocation>;
237
283
  readonly onGeofenceEvent: CodegenTypes.EventEmitter<WireGeofenceEvent>;
238
284
  readonly onSyncStatus: CodegenTypes.EventEmitter<WireSyncStatus>;
239
285
  readonly onAuthTokens: CodegenTypes.EventEmitter<WireAuthTokens>;
240
286
  readonly onLicenseStatus: CodegenTypes.EventEmitter<WireLicenseStatus>;
287
+ readonly onLog: CodegenTypes.EventEmitter<WireLogEntry>;
241
288
  }
242
289
 
243
290
  export default TurboModuleRegistry.getEnforcing<Spec>('BeekonRn');
package/src/beekon.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import NativeBeekon from './NativeBeekonRn';
2
+ import { LicenseNudge } from './internal/licenseNudge';
2
3
  import type { AuthTokens } from './types/auth';
3
4
  import type { BeekonConfig } from './types/config';
4
- import type { AccuracyMode } from './types/enums';
5
+ import type { AccuracyMode, LogLevel } from './types/enums';
5
6
  import type { BeekonGeofence, GeofenceEvent } from './types/geofence';
6
7
  import type { LicenseStatus } from './types/license';
7
8
  import type { Location } from './types/location';
9
+ import type { LogEntry } from './types/log';
8
10
  import type { BeekonState } from './types/state';
9
11
  import type { SyncStatus } from './types/sync';
10
12
  import {
@@ -17,6 +19,7 @@ import {
17
19
  wireToGeofenceEvent,
18
20
  wireToLicenseStatus,
19
21
  wireToLocation,
22
+ wireToLogEntry,
20
23
  wireToState,
21
24
  wireToSyncStatus,
22
25
  } from './internal/mappers';
@@ -42,6 +45,7 @@ class EventHub {
42
45
  private readonly syncListeners = new Set<Listener<SyncStatus>>();
43
46
  private readonly authTokenListeners = new Set<Listener<AuthTokens>>();
44
47
  private readonly licenseListeners = new Set<Listener<LicenseStatus>>();
48
+ private readonly logListeners = new Set<Listener<LogEntry>>();
45
49
  private lastState: BeekonState | undefined;
46
50
  private lastSyncStatus: SyncStatus | undefined;
47
51
  private lastAuthTokens: AuthTokens | undefined;
@@ -79,6 +83,10 @@ class EventHub {
79
83
  this.lastLicenseStatus = s;
80
84
  this.licenseListeners.forEach((cb) => cb(s));
81
85
  });
86
+ NativeBeekon.onLog((w) => {
87
+ const e = wireToLogEntry(w);
88
+ this.logListeners.forEach((cb) => cb(e));
89
+ });
82
90
  }
83
91
 
84
92
  onState(cb: Listener<BeekonState>): () => void {
@@ -132,6 +140,14 @@ class EventHub {
132
140
  this.licenseListeners.delete(cb);
133
141
  };
134
142
  }
143
+
144
+ onLog(cb: Listener<LogEntry>): () => void {
145
+ this.ensureSubscribed();
146
+ this.logListeners.add(cb);
147
+ return () => {
148
+ this.logListeners.delete(cb);
149
+ };
150
+ }
135
151
  }
136
152
 
137
153
  /**
@@ -154,6 +170,20 @@ class EventHub {
154
170
  */
155
171
  class BeekonImpl {
156
172
  private readonly hub = new EventHub();
173
+ private readonly licenseNudge = new LicenseNudge();
174
+ private licenseNudgeAttached = false;
175
+
176
+ /**
177
+ * Echo the evaluation-mode nudge to the JS console once, on first lifecycle
178
+ * use — so it only activates for apps actually using the SDK. Metro shows JS
179
+ * console only, so this is how the native nudge reaches an RN dev. See
180
+ * {@link LicenseNudge}.
181
+ */
182
+ private activateLicenseNudge(): void {
183
+ if (this.licenseNudgeAttached) return;
184
+ this.licenseNudgeAttached = true;
185
+ this.licenseNudge.attach((cb) => this.hub.onLicenseStatus(cb));
186
+ }
157
187
 
158
188
  /**
159
189
  * Set tracking parameters. Optional — `start()` falls back to the previously
@@ -163,6 +193,7 @@ class BeekonImpl {
163
193
  */
164
194
  async configure(config: BeekonConfig): Promise<void> {
165
195
  this.hub.ensureSubscribed();
196
+ this.activateLicenseNudge();
166
197
  await NativeBeekon.configure(configToWire(config));
167
198
  }
168
199
 
@@ -174,6 +205,7 @@ class BeekonImpl {
174
205
  */
175
206
  async start(): Promise<void> {
176
207
  this.hub.ensureSubscribed();
208
+ this.activateLicenseNudge();
177
209
  await NativeBeekon.start();
178
210
  }
179
211
 
@@ -194,6 +226,7 @@ class BeekonImpl {
194
226
  */
195
227
  async resumeIfNeeded(): Promise<void> {
196
228
  this.hub.ensureSubscribed();
229
+ this.activateLicenseNudge();
197
230
  await NativeBeekon.resumeIfNeeded();
198
231
  }
199
232
 
@@ -321,6 +354,63 @@ class BeekonImpl {
321
354
  return wireToLicenseStatus(await NativeBeekon.licenseStatus());
322
355
  }
323
356
 
357
+ /**
358
+ * Read persisted diagnostic log entries in the inclusive range `[from, to]`,
359
+ * oldest first. Entries survive process death, so this recovers what the SDK
360
+ * did during a background session — the key to diagnosing field issues. Throws
361
+ * `BeekonError` with kind `'storage'` on a read failure. Requires native ≥ 0.0.9.
362
+ */
363
+ async getLog(from: Date, to: Date): Promise<LogEntry[]> {
364
+ try {
365
+ const wires = await NativeBeekon.getLog(from.getTime(), to.getTime());
366
+ return wires.map(wireToLogEntry);
367
+ } catch (e) {
368
+ rethrowAsBeekonError(e);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Serialize the whole diagnostic log buffer to an NDJSON file and resolve its
374
+ * path. The app owns the file — attach it to a support email, share it, or
375
+ * upload it. Throws `BeekonError` with kind `'storage'` on a failure. Requires
376
+ * native ≥ 0.0.9.
377
+ */
378
+ async exportLog(): Promise<string> {
379
+ try {
380
+ return await NativeBeekon.exportLog();
381
+ } catch (e) {
382
+ rethrowAsBeekonError(e);
383
+ }
384
+ }
385
+
386
+ /** Delete all persisted diagnostic log entries. Requires native ≥ 0.0.9. */
387
+ async clearLog(): Promise<void> {
388
+ try {
389
+ await NativeBeekon.clearLog();
390
+ } catch (e) {
391
+ rethrowAsBeekonError(e);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Set the diagnostic log verbosity threshold at runtime — no restart needed.
397
+ * Raise to `'debug'`/`'verbose'` for field diagnosis, then lower again. A later
398
+ * `configure` re-applies `BeekonConfig.logLevel` and supersedes this. Requires
399
+ * native ≥ 0.0.9.
400
+ */
401
+ async setLogLevel(level: LogLevel): Promise<void> {
402
+ await NativeBeekon.setLogLevel(level);
403
+ }
404
+
405
+ /**
406
+ * Write a host-app breadcrumb into the same pipeline as the SDK's own logs
407
+ * (category `app`), so app and SDK events interleave in one timeline. Requires
408
+ * native ≥ 0.0.9.
409
+ */
410
+ async log(level: LogLevel, message: string): Promise<void> {
411
+ await NativeBeekon.log(level, message);
412
+ }
413
+
324
414
  /**
325
415
  * Subscribe to tracking-state transitions (`idle` / `tracking` /
326
416
  * `stopped(reason)`). The current state is delivered to new subscribers
@@ -375,6 +465,15 @@ class BeekonImpl {
375
465
  onLicenseStatus(cb: (s: LicenseStatus) => void): () => void {
376
466
  return this.hub.onLicenseStatus(cb);
377
467
  }
468
+
469
+ /**
470
+ * Subscribe to diagnostic log entries as they are recorded — SDK subsystems
471
+ * plus host breadcrumbs from {@link log}. Broadcast (no replay); read history
472
+ * with {@link getLog}. Returns an unsubscribe function. Requires native ≥ 0.0.9.
473
+ */
474
+ onLog(cb: (e: LogEntry) => void): () => void {
475
+ return this.hub.onLog(cb);
476
+ }
378
477
  }
379
478
 
380
479
  export const Beekon = new BeekonImpl();
package/src/index.tsx CHANGED
@@ -1,7 +1,11 @@
1
1
  export { Beekon } from './beekon';
2
2
 
3
+ // `BeekonConfig` is a merged type + value (the `cloud` / `selfManaged`
4
+ // factories), so it is a value export — its type meaning travels with it.
5
+ export { BeekonConfig } from './types/config';
3
6
  export type {
4
- BeekonConfig,
7
+ CloudConfig,
8
+ SelfManagedConfig,
5
9
  SyncConfig,
6
10
  NotificationConfig,
7
11
  } from './types/config';
@@ -14,7 +18,9 @@ export type {
14
18
  ActivityType,
15
19
  AuthStrategy,
16
20
  AuthBodyFormat,
21
+ LogLevel,
17
22
  } from './types/enums';
23
+ export type { LogEntry } from './types/log';
18
24
  export type { AuthConfig, AuthResponseMapping, AuthTokens } from './types/auth';
19
25
  export type { Location } from './types/location';
20
26
  export type {
@@ -0,0 +1,80 @@
1
+ import type { LicenseStatus } from '../types/license';
2
+
3
+ // The debug nudge: a prominent multi-line banner mirroring the native debug
4
+ // banner. Every line carries the prefix (greppability). Neutral by design — no
5
+ // "release is unaffected", no advertised opt-out — so it nudges toward a key.
6
+ const BANNER = [
7
+ '[Beekon][license] ════════════════════════════════════════════════════',
8
+ '[Beekon][license] Beekon is running without a license key.',
9
+ '[Beekon][license] Get one: https://getbeekon.com/pricing',
10
+ '[Beekon][license] Then add it via BeekonConfig.licenseKey.',
11
+ '[Beekon][license] ════════════════════════════════════════════════════',
12
+ ].join('\n');
13
+
14
+ function isDev(): boolean {
15
+ const g = globalThis as { __DEV__?: boolean };
16
+ return g.__DEV__ === true;
17
+ }
18
+
19
+ function acknowledged(): boolean {
20
+ const g = globalThis as { BEEKON_EVALUATION_MODE?: boolean };
21
+ return g.BEEKON_EVALUATION_MODE === true;
22
+ }
23
+
24
+ function defaultLog(message: string): void {
25
+ // The dev-console nudge: Metro surfaces JS console output, not native logs.
26
+ console.warn(message);
27
+ }
28
+
29
+ /**
30
+ * Echoes the evaluation-mode license nudge into the JS console.
31
+ *
32
+ * The native SDKs log the license notice to the platform stream (iOS unified
33
+ * logging, Android Logcat), but Metro shows only JS `console.*` — neither native
34
+ * stream reaches it, so a React Native developer never sees the "you're
35
+ * unlicensed" nudge in the terminal they actually watch. This mirrors the
36
+ * **evaluation** notice (no key supplied) onto `console.warn` so it surfaces in
37
+ * Metro, on **both** platforms.
38
+ *
39
+ * Debug-only (`__DEV__`): a release bundle never console-warns. Fires **at most
40
+ * once per JS context** on the first `evaluation` status, mirroring the native
41
+ * once-per-transition guarantee. Purely a log line — it never blocks, degrades,
42
+ * or delays the SDK (license-format-v1 policy anchor).
43
+ *
44
+ * Silence it without a key by setting `globalThis.BEEKON_EVALUATION_MODE = true`
45
+ * before the SDK initializes — the JS analog of the native `BeekonEvaluationMode`
46
+ * plist / `in.wayq.beekon.evaluationMode` manifest acknowledgment.
47
+ */
48
+ export class LicenseNudge {
49
+ private emitted = false;
50
+ private unsubscribe: (() => void) | undefined;
51
+ private readonly log: (message: string) => void;
52
+ private readonly enabled: boolean;
53
+
54
+ constructor(options?: {
55
+ log?: (message: string) => void;
56
+ enabled?: boolean;
57
+ }) {
58
+ this.log = options?.log ?? defaultLog;
59
+ this.enabled = options?.enabled ?? (isDev() && !acknowledged());
60
+ }
61
+
62
+ /**
63
+ * Subscribe via [subscribe] — a replay-1 license-status subscription that
64
+ * returns an unsubscribe function — and emit the banner the first time the
65
+ * status is `evaluation`. A no-op when disabled; idempotent.
66
+ */
67
+ attach(subscribe: (cb: (status: LicenseStatus) => void) => () => void): void {
68
+ if (!this.enabled || this.unsubscribe !== undefined) return;
69
+ this.unsubscribe = subscribe((status) => this.onStatus(status));
70
+ }
71
+
72
+ private onStatus(status: LicenseStatus): void {
73
+ if (this.emitted || status.status !== 'evaluation') return;
74
+ this.emitted = true;
75
+ this.log(BANNER);
76
+ // One-shot per process — drop the subscription once nudged.
77
+ this.unsubscribe?.();
78
+ this.unsubscribe = undefined;
79
+ }
80
+ }
@@ -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,
@@ -29,6 +31,7 @@ import type {
29
31
  WireKeyValue,
30
32
  WireLicenseStatus,
31
33
  WireLocation,
34
+ WireLogEntry,
32
35
  WireState,
33
36
  WireSyncStatus,
34
37
  } from '../NativeBeekonRn';
@@ -44,6 +47,7 @@ const DEFAULTS = {
44
47
  detectActivity: false,
45
48
  syncIntervalSeconds: 300,
46
49
  syncBatchSize: 100,
50
+ syncThreshold: 0,
47
51
  authStrategy: 'bearer' as AuthStrategy,
48
52
  authBodyFormat: 'form' as AuthBodyFormat,
49
53
  authSkewMarginSeconds: 60,
@@ -51,6 +55,7 @@ const DEFAULTS = {
51
55
  string,
52
56
  string
53
57
  >,
58
+ logLevel: 'info' as LogLevel,
54
59
  };
55
60
 
56
61
  // --- Public → wire ---------------------------------------------------------
@@ -62,9 +67,53 @@ export function recordToEntries(
62
67
  return Object.keys(record).map((key) => ({ key, value: record[key]! }));
63
68
  }
64
69
 
70
+ function notificationToWire(config: BeekonConfig): WireConfig['notification'] {
71
+ return config.notification
72
+ ? {
73
+ title: config.notification.title,
74
+ text: config.notification.text,
75
+ smallIcon: config.notification.smallIcon,
76
+ }
77
+ : undefined;
78
+ }
79
+
80
+ // Switches on the sealed-config arm (cloud-mode-v1 §2). The wire form is flat
81
+ // and carries both arms' slots; only the active arm's fields are populated. The
82
+ // tracking-param slots are filled with defaults regardless so the wire struct
83
+ // stays fully-populated — the native cloud arm ignores them and derives config
84
+ // from the server.
65
85
  export function configToWire(config: BeekonConfig): WireConfig {
86
+ const tracking = {
87
+ minTimeBetweenLocationsSeconds: DEFAULTS.minTimeBetweenLocationsSeconds,
88
+ minDistanceBetweenLocationsMeters:
89
+ DEFAULTS.minDistanceBetweenLocationsMeters,
90
+ accuracyMode: DEFAULTS.accuracyMode,
91
+ whenStationary: DEFAULTS.whenStationary,
92
+ stationaryRadiusMeters: DEFAULTS.stationaryRadiusMeters,
93
+ detectActivity: DEFAULTS.detectActivity,
94
+ };
95
+
96
+ if (config.mode === 'cloud') {
97
+ return {
98
+ mode: 'cloud',
99
+ // Passed through verbatim; the native cloud arm trims and validates it.
100
+ projectKey: config.projectKey,
101
+ // Omitted means the native baked-in default (https://api.getbeekon.com).
102
+ endpoint: config.endpoint,
103
+ ...tracking,
104
+ // Cloud config is server-owned: no local sync or license key.
105
+ sync: undefined,
106
+ notification: notificationToWire(config),
107
+ licenseKey: undefined,
108
+ logLevel: config.logLevel ?? DEFAULTS.logLevel,
109
+ };
110
+ }
111
+
66
112
  const sync = config.sync;
67
113
  return {
114
+ mode: 'selfManaged',
115
+ projectKey: undefined,
116
+ endpoint: undefined,
68
117
  minTimeBetweenLocationsSeconds:
69
118
  config.minTimeBetweenLocationsSeconds ??
70
119
  DEFAULTS.minTimeBetweenLocationsSeconds,
@@ -82,20 +131,16 @@ export function configToWire(config: BeekonConfig): WireConfig {
82
131
  headers: recordToEntries(sync.headers),
83
132
  intervalSeconds: sync.intervalSeconds ?? DEFAULTS.syncIntervalSeconds,
84
133
  batchSize: sync.batchSize ?? DEFAULTS.syncBatchSize,
134
+ syncThreshold: sync.syncThreshold ?? DEFAULTS.syncThreshold,
85
135
  auth: sync.auth ? authToWire(sync.auth) : undefined,
86
136
  }
87
137
  : undefined,
88
- notification: config.notification
89
- ? {
90
- title: config.notification.title,
91
- text: config.notification.text,
92
- smallIcon: config.notification.smallIcon,
93
- }
94
- : undefined,
138
+ notification: notificationToWire(config),
95
139
  // Passed through verbatim — `undefined` is omitted and the native SDK falls
96
140
  // through to manifest/Info.plist. No wrapper-side trimming or fallback
97
141
  // (blank/whitespace handling lives in the native SDK; spec §9).
98
142
  licenseKey: config.licenseKey,
143
+ logLevel: config.logLevel ?? DEFAULTS.logLevel,
99
144
  };
100
145
  }
101
146
 
@@ -201,6 +246,20 @@ export function wireToGeofenceEvent(w: WireGeofenceEvent): GeofenceEvent {
201
246
  };
202
247
  }
203
248
 
249
+ export function wireToLogEntry(w: WireLogEntry): LogEntry {
250
+ return {
251
+ id: w.id,
252
+ timestamp: new Date(w.timestampMs),
253
+ level: oneOf<LogLevel>(
254
+ w.level,
255
+ ['off', 'error', 'warn', 'info', 'debug', 'verbose'],
256
+ 'info'
257
+ ),
258
+ category: w.category,
259
+ message: w.message,
260
+ };
261
+ }
262
+
204
263
  export function wireToState(w: WireState): BeekonState {
205
264
  switch (w.type) {
206
265
  case 'idle':
@@ -290,6 +349,7 @@ function toStopReason(s: string | undefined): StopReason {
290
349
  'permissionDenied',
291
350
  'locationServicesDisabled',
292
351
  'locationUnavailable',
352
+ 'cloudModeUnavailable',
293
353
  'system',
294
354
  ],
295
355
  // The native side always populates the reason for a `stopped` state; an