react-native-nitro-compass 1.0.9 → 1.2.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 (47) hide show
  1. package/README.md +166 -20
  2. package/android/src/main/java/com/margelo/nitro/nitrocompass/HybridNitroCompass.kt +718 -105
  3. package/ios/HybridNitroCompass.swift +119 -6
  4. package/lib/commonjs/hook.js +102 -11
  5. package/lib/commonjs/hook.js.map +1 -1
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/multiplex.js +23 -2
  8. package/lib/commonjs/multiplex.js.map +1 -1
  9. package/lib/module/hook.js +103 -12
  10. package/lib/module/hook.js.map +1 -1
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/multiplex.js +23 -2
  13. package/lib/module/multiplex.js.map +1 -1
  14. package/lib/typescript/src/hook.d.ts +49 -1
  15. package/lib/typescript/src/hook.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +2 -2
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/lib/typescript/src/multiplex.d.ts.map +1 -1
  19. package/lib/typescript/src/specs/NitroCompass.nitro.d.ts +158 -18
  20. package/lib/typescript/src/specs/NitroCompass.nitro.d.ts.map +1 -1
  21. package/nitrogen/generated/android/c++/JCompassSample.hpp +7 -3
  22. package/nitrogen/generated/android/c++/JDebugInfo.hpp +85 -0
  23. package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.cpp +21 -0
  24. package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.hpp +4 -0
  25. package/nitrogen/generated/android/c++/JSensorKind.hpp +6 -3
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/CompassSample.kt +9 -4
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/DebugInfo.kt +86 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/HybridNitroCompassSpec.kt +16 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/SensorKind.kt +4 -3
  30. package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Bridge.hpp +12 -0
  31. package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Umbrella.hpp +3 -0
  32. package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.hpp +29 -0
  33. package/nitrogen/generated/ios/swift/CompassSample.swift +7 -2
  34. package/nitrogen/generated/ios/swift/DebugInfo.swift +64 -0
  35. package/nitrogen/generated/ios/swift/HybridNitroCompassSpec.swift +4 -0
  36. package/nitrogen/generated/ios/swift/HybridNitroCompassSpec_cxx.swift +45 -0
  37. package/nitrogen/generated/ios/swift/SensorKind.swift +8 -4
  38. package/nitrogen/generated/shared/c++/CompassSample.hpp +6 -2
  39. package/nitrogen/generated/shared/c++/DebugInfo.hpp +111 -0
  40. package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.cpp +4 -0
  41. package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.hpp +7 -0
  42. package/nitrogen/generated/shared/c++/SensorKind.hpp +10 -6
  43. package/package.json +2 -2
  44. package/src/hook.ts +161 -12
  45. package/src/index.ts +2 -0
  46. package/src/multiplex.ts +23 -2
  47. package/src/specs/NitroCompass.nitro.ts +164 -18
@@ -43,6 +43,18 @@ class HybridNitroCompass: HybridNitroCompassSpec {
43
43
  return q
44
44
  }()
45
45
  private var lastInterference: Bool?
46
+ // Most recent calibrated magnetic-field magnitude (µT). Surfaced on
47
+ // every CompassSample so consumers can render a strength meter; -1
48
+ // until CMDeviceMotion produces its first calibrated reading.
49
+ private var lastFieldUt: Double = -1
50
+ // Counter of consecutive non-uncalibrated motion samples. CoreMotion's
51
+ // bias estimate takes a second or two of normal motion to converge
52
+ // after subscribe; emitting interference transitions before then
53
+ // produces false positives because the early "calibrated" field is
54
+ // dominated by the device's own internal bias. Wait for N stable
55
+ // samples before trusting the magnitude reading.
56
+ private var motionStableSampleCount: Int = 0
57
+ private static let motionStableSampleThreshold: Int = 5
46
58
  private var pauseOnBackground: Bool = true
47
59
  private var started: Bool = false
48
60
  private var isSubscribed: Bool = false
@@ -59,7 +71,10 @@ class HybridNitroCompass: HybridNitroCompassSpec {
59
71
  super.init()
60
72
  let setup = {
61
73
  let m = CLLocationManager()
62
- m.headingFilter = 1
74
+ // headingFilter is overwritten by subscribe() each start(); the
75
+ // init value is only relevant if a caller queried headingFilter
76
+ // before start, which nothing does. Pick the default that matches
77
+ // activeFilterDegrees so a stray query is consistent.
63
78
  m.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
64
79
  self.manager = m
65
80
  }
@@ -183,11 +198,35 @@ class HybridNitroCompass: HybridNitroCompassSpec {
183
198
  }
184
199
  }
185
200
 
201
+ // CLLocationManager filters heading internally with Apple's own
202
+ // algorithm; layering an EMA on top would only add latency. Kept as a
203
+ // no-op so cross-platform JS callers can call it unconditionally.
204
+ func setSmoothing(alpha: Double) throws {}
205
+
186
206
  func getDiagnostics() throws -> SensorDiagnostics? {
187
207
  guard CLLocationManager.headingAvailable() else { return nil }
188
208
  return SensorDiagnostics(sensor: .corelocation)
189
209
  }
190
210
 
211
+ // iOS lets CLLocationManager handle the gyro+mag fusion + bias
212
+ // estimation internally, so most of the Android-side debug fields
213
+ // have no analogue on this side. We surface the two we *can*
214
+ // observe (current interference state, last field strength) and
215
+ // report sentinel "n/a" values for the rest, matching the iOS
216
+ // notes in the DebugInfo doc.
217
+ func getDebugInfo() throws -> DebugInfo {
218
+ return DebugInfo(
219
+ interferenceActive: lastInterference == true,
220
+ msSinceLastBiasJump: -1,
221
+ expectedFieldMicroTesla: -1,
222
+ lastFieldMicroTesla: lastFieldUt,
223
+ fusedYawDeg: Double.nan,
224
+ lastYawRateDegPerS: 0,
225
+ hasGameRotationVector: false,
226
+ usingUncalibratedMag: false
227
+ )
228
+ }
229
+
191
230
  func getCurrentHeading() throws -> CompassSample? {
192
231
  return lastSample
193
232
  }
@@ -196,8 +235,18 @@ class HybridNitroCompass: HybridNitroCompassSpec {
196
235
  declinationDeg = degrees
197
236
  }
198
237
 
238
+ // No-op on iOS — `CLLocationManager` already uses GPS-derived location
239
+ // internally for all field-related reasoning (true-north, declination,
240
+ // expected field strength). Layering our own lookup on top would be
241
+ // redundant. Provided for cross-platform JS callers.
242
+ func setLocation(latitude: Double, longitude: Double) throws {}
243
+
199
244
  func setOnCalibrationNeeded(onChange: @escaping (_ quality: AccuracyQuality) -> Void) throws {
200
245
  calibrationCb = onChange
246
+ // Replay current bucket so a late-registering consumer sees the
247
+ // truth instead of waiting for the next transition. Mirrors the
248
+ // setOnInterferenceDetected behavior.
249
+ if let last = lastQuality { onChange(last) }
201
250
  }
202
251
 
203
252
  func setOnInterferenceDetected(onChange: @escaping (_ interferenceDetected: Bool) -> Void) throws {
@@ -217,6 +266,30 @@ class HybridNitroCompass: HybridNitroCompassSpec {
217
266
  }
218
267
  }
219
268
 
269
+ func recalibrate() throws {
270
+ // Stop/start the heading subscription and reset every cached
271
+ // signal. Apple's CLLocationManager handles soft/hard-iron
272
+ // calibration internally — the recovery itself still requires the
273
+ // user to move the device through varying orientations — but
274
+ // dropping our own cached state ensures the next callback isn't
275
+ // suppressed by stale `lastQuality` / `lastInterference`
276
+ // comparisons, and dismisses any pending heading-calibration
277
+ // overlay so the consumer can prompt fresh.
278
+ guard started else { return }
279
+ // dismissHeadingCalibrationDisplay touches UIKit-adjacent state and
280
+ // must run on main. recalibrate() may be called from the JS thread.
281
+ DispatchQueue.main.async { [weak self] in
282
+ self?.manager.dismissHeadingCalibrationDisplay()
283
+ }
284
+ let wasSubscribed = isSubscribed
285
+ if wasSubscribed { unsubscribe() }
286
+ lastSample = nil
287
+ lastQuality = nil
288
+ lastInterference = nil
289
+ lastFieldUt = -1
290
+ if wasSubscribed { subscribe() }
291
+ }
292
+
220
293
  func getPermissionStatus() throws -> PermissionStatus {
221
294
  return Self.mapAuthStatus(manager.authorizationStatus)
222
295
  }
@@ -291,12 +364,28 @@ class HybridNitroCompass: HybridNitroCompassSpec {
291
364
  guard motionManager.isDeviceMotionAvailable,
292
365
  !motionManager.isDeviceMotionActive else { return }
293
366
  motionManager.deviceMotionUpdateInterval = 0.2 // 5Hz
367
+ motionStableSampleCount = 0
294
368
  motionManager.startDeviceMotionUpdates(to: motionQueue) { [weak self] motion, _ in
295
369
  guard let self = self, let m = motion else { return }
296
370
  let cal = m.magneticField
297
- if cal.accuracy == .uncalibrated { return }
371
+ if cal.accuracy == .uncalibrated {
372
+ // Bias estimate not yet usable. Reset stability counter so the
373
+ // post-convergence count starts fresh.
374
+ self.motionStableSampleCount = 0
375
+ return
376
+ }
298
377
  let f = cal.field
299
378
  let magnitude = sqrt(f.x * f.x + f.y * f.y + f.z * f.z)
379
+ self.lastFieldUt = magnitude
380
+
381
+ // Wait for N consecutive non-uncalibrated samples before letting
382
+ // magnitude drive interference transitions. Without this, the
383
+ // first second post-subscribe routinely fires false positives
384
+ // because the bias estimate hasn't converged yet.
385
+ if self.motionStableSampleCount < Self.motionStableSampleThreshold {
386
+ self.motionStableSampleCount += 1
387
+ return
388
+ }
300
389
  self.evaluateInterference(magnitude: magnitude)
301
390
  }
302
391
  }
@@ -305,6 +394,7 @@ class HybridNitroCompass: HybridNitroCompassSpec {
305
394
  if motionManager.isDeviceMotionActive {
306
395
  motionManager.stopDeviceMotionUpdates()
307
396
  }
397
+ motionStableSampleCount = 0
308
398
  }
309
399
 
310
400
  private func evaluateInterference(magnitude: Double) {
@@ -354,23 +444,33 @@ class HybridNitroCompass: HybridNitroCompassSpec {
354
444
  lastSample = nil
355
445
  lastQuality = nil
356
446
  lastInterference = nil
447
+ lastFieldUt = -1
357
448
  }
358
449
 
359
450
  private func deliver(heading magnetic: Double, accuracy: Double) {
360
451
  var heading = magnetic + declinationDeg
361
452
  heading = heading.truncatingRemainder(dividingBy: 360)
362
453
  if heading < 0 { heading += 360 }
363
- let sample = CompassSample(heading: heading, accuracy: accuracy)
454
+ let sample = CompassSample(
455
+ heading: heading,
456
+ accuracy: accuracy,
457
+ fieldStrengthMicroTesla: lastFieldUt
458
+ )
364
459
  lastSample = sample
365
460
 
461
+ // CLLocationManager.headingAccuracy is conservative — even a
462
+ // well-calibrated compass usually reports 5–15°, and values under
463
+ // 5° are basically never produced. Bucket against the realistic
464
+ // distribution so `.high` is reachable, mirroring how Android maps
465
+ // SENSOR_STATUS_ACCURACY_HIGH.
366
466
  let quality: AccuracyQuality
367
467
  if accuracy < 0 {
368
468
  quality = .unreliable
369
- } else if accuracy < 5 {
469
+ } else if accuracy < 20 {
370
470
  quality = .high
371
- } else if accuracy < 15 {
471
+ } else if accuracy < 35 {
372
472
  quality = .medium
373
- } else if accuracy < 30 {
473
+ } else if accuracy < 55 {
374
474
  quality = .low
375
475
  } else {
376
476
  quality = .unreliable
@@ -387,6 +487,19 @@ class HybridNitroCompass: HybridNitroCompassSpec {
387
487
  }
388
488
 
389
489
  private func applyHeadingOrientation() {
490
+ // Prefer device orientation for face-up / face-down — those don't
491
+ // appear in interfaceOrientation but matter for headings on tablets
492
+ // held flat. Fall back to interfaceOrientation for the standard
493
+ // four cases (which respects app rotation locks).
494
+ let device = UIDevice.current.orientation
495
+ if device == .faceUp {
496
+ manager.headingOrientation = .faceUp
497
+ return
498
+ }
499
+ if device == .faceDown {
500
+ manager.headingOrientation = .faceDown
501
+ return
502
+ }
390
503
  let scene = UIApplication.shared.connectedScenes
391
504
  .compactMap { $0 as? UIWindowScene }
392
505
  .first
@@ -17,6 +17,7 @@ var _multiplex = require("./multiplex");
17
17
  function useCompass(options = {}) {
18
18
  const {
19
19
  filterDegrees = 1,
20
+ smoothingAlpha = 0.2,
20
21
  declination = 0,
21
22
  pauseOnBackground = true,
22
23
  enabled = true
@@ -24,17 +25,46 @@ function useCompass(options = {}) {
24
25
  const [reading, setReading] = (0, _react.useState)(null);
25
26
  const [quality, setQuality] = (0, _react.useState)(null);
26
27
  const [interfering, setInterfering] = (0, _react.useState)(false);
27
- const [hasCompass] = (0, _react.useState)(() => _native.NitroCompass.hasCompass());
28
- const [diagnostics] = (0, _react.useState)(() => _native.NitroCompass.getDiagnostics());
29
28
 
30
- // Tracked via ref so the heading-subscription effect can re-apply
31
- // the user's filter after a stop/start cycle without restarting on
32
- // every filterDegrees change.
29
+ // Wrap in try/catch so a missing/misconfigured native module doesn't
30
+ // throw during render return safe defaults and let the host UI
31
+ // surface "no compass". Without this, the throw bubbles up and
32
+ // becomes a render error that blanks the screen.
33
+ const [hasCompass] = (0, _react.useState)(() => {
34
+ try {
35
+ return _native.NitroCompass.hasCompass();
36
+ } catch {
37
+ return false;
38
+ }
39
+ });
40
+ const [diagnostics] = (0, _react.useState)(() => {
41
+ try {
42
+ return _native.NitroCompass.getDiagnostics();
43
+ } catch {
44
+ return undefined;
45
+ }
46
+ });
47
+ const [permission, setPermission] = (0, _react.useState)(() => {
48
+ try {
49
+ return _native.NitroCompass.getPermissionStatus();
50
+ } catch {
51
+ return 'unknown';
52
+ }
53
+ });
54
+
55
+ // Tracked via refs so the heading-subscription effect can re-apply
56
+ // the user's filter and smoothing after a stop/start cycle without
57
+ // restarting on every option change.
33
58
  const filterRef = (0, _react.useRef)(filterDegrees);
34
59
  filterRef.current = filterDegrees;
60
+ const smoothingRef = (0, _react.useRef)(smoothingAlpha);
61
+ smoothingRef.current = smoothingAlpha;
35
62
  (0, _react.useEffect)(() => {
36
63
  _native.NitroCompass.setFilter(filterDegrees);
37
64
  }, [filterDegrees]);
65
+ (0, _react.useEffect)(() => {
66
+ _native.NitroCompass.setSmoothing(smoothingAlpha);
67
+ }, [smoothingAlpha]);
38
68
  (0, _react.useEffect)(() => {
39
69
  _native.NitroCompass.setDeclination(declination);
40
70
  }, [declination]);
@@ -50,20 +80,81 @@ function useCompass(options = {}) {
50
80
  return (0, _multiplex.addInterferenceListener)(setInterfering);
51
81
  }, [hasCompass]);
52
82
  (0, _react.useEffect)(() => {
53
- if (!hasCompass || !enabled) return;
54
- const off = (0, _multiplex.addHeadingListener)(setReading);
55
- // Multiplex starts the sensor with a default filter; re-apply the
56
- // current option after subscribing.
57
- _native.NitroCompass.setFilter(filterRef.current);
83
+ if (!hasCompass || !enabled) {
84
+ // When the user disables the hook, clear stale UI state.
85
+ // Without this, `reading` / `interfering` keep their last value
86
+ // forever, so the consumer's UI can't tell "subscription is off"
87
+ // from "compass is currently quiet".
88
+ setReading(null);
89
+ setInterfering(false);
90
+ return;
91
+ }
92
+ let off;
93
+ try {
94
+ off = (0, _multiplex.addHeadingListener)(setReading);
95
+ // Multiplex starts the sensor with a default filter; re-apply
96
+ // the current options after subscribing. recalibrate() can also
97
+ // reset native smoothing state, so we pin our chosen alpha back
98
+ // here as well.
99
+ _native.NitroCompass.setFilter(filterRef.current);
100
+ _native.NitroCompass.setSmoothing(smoothingRef.current);
101
+ } catch (e) {
102
+ // start() throws on iOS when location authorization is denied.
103
+ // Swallow it here so the hook tree doesn't unmount; consumers
104
+ // should call NitroCompass.requestPermission() explicitly before
105
+ // setting enabled=true if they want to recover.
106
+ // eslint-disable-next-line no-console
107
+ console.warn('[NitroCompass] failed to start heading subscription:', e);
108
+ }
58
109
  return off;
59
110
  // eslint-disable-next-line react-hooks/exhaustive-deps
60
111
  }, [hasCompass, enabled]);
112
+
113
+ // Stable callbacks so consumers' useEffect deps don't churn on every
114
+ // render. All four are thin wrappers around NitroCompass; the hook's
115
+ // own state isn't reactive to recalibrate/setLocation since neither
116
+ // changes any field returned here.
117
+ const getCurrentHeading = (0, _react.useCallback)(() => {
118
+ try {
119
+ return _native.NitroCompass.getCurrentHeading();
120
+ } catch {
121
+ return undefined;
122
+ }
123
+ }, []);
124
+ const recalibrate = (0, _react.useCallback)(() => {
125
+ try {
126
+ _native.NitroCompass.recalibrate();
127
+ } catch {
128
+ // Native missing — nothing to do.
129
+ }
130
+ }, []);
131
+ const setLocation = (0, _react.useCallback)((latitude, longitude) => {
132
+ try {
133
+ _native.NitroCompass.setLocation(latitude, longitude);
134
+ } catch {
135
+ // Native missing — silently ignored.
136
+ }
137
+ }, []);
138
+ const requestPermission = (0, _react.useCallback)(async () => {
139
+ try {
140
+ const status = await _native.NitroCompass.requestPermission();
141
+ setPermission(status);
142
+ return status;
143
+ } catch {
144
+ return 'denied';
145
+ }
146
+ }, []);
61
147
  return {
62
148
  reading,
63
149
  quality,
64
150
  interfering,
65
151
  hasCompass,
66
- diagnostics
152
+ diagnostics,
153
+ permission,
154
+ getCurrentHeading,
155
+ recalibrate,
156
+ setLocation,
157
+ requestPermission
67
158
  };
68
159
  }
69
160
  //# sourceMappingURL=hook.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_native","_multiplex","useCompass","options","filterDegrees","declination","pauseOnBackground","enabled","reading","setReading","useState","quality","setQuality","interfering","setInterfering","hasCompass","NitroCompass","diagnostics","getDiagnostics","filterRef","useRef","current","useEffect","setFilter","setDeclination","setPauseOnBackground","addCalibrationListener","addInterferenceListener","off","addHeadingListener"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAMA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,UAAA,GAAAF,OAAA;AA+CA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASG,UAAUA,CACxBC,OAA0B,GAAG,CAAC,CAAC,EACb;EAClB,MAAM;IACJC,aAAa,GAAG,CAAC;IACjBC,WAAW,GAAG,CAAC;IACfC,iBAAiB,GAAG,IAAI;IACxBC,OAAO,GAAG;EACZ,CAAC,GAAGJ,OAAO;EAEX,MAAM,CAACK,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAC,eAAQ,EAAuB,IAAI,CAAC;EAClE,MAAM,CAACC,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAF,eAAQ,EAAyB,IAAI,CAAC;EACpE,MAAM,CAACG,WAAW,EAAEC,cAAc,CAAC,GAAG,IAAAJ,eAAQ,EAAC,KAAK,CAAC;EAErD,MAAM,CAACK,UAAU,CAAC,GAAG,IAAAL,eAAQ,EAAC,MAAMM,oBAAY,CAACD,UAAU,CAAC,CAAC,CAAC;EAC9D,MAAM,CAACE,WAAW,CAAC,GAAG,IAAAP,eAAQ,EAAC,MAAMM,oBAAY,CAACE,cAAc,CAAC,CAAC,CAAC;;EAEnE;EACA;EACA;EACA,MAAMC,SAAS,GAAG,IAAAC,aAAM,EAAChB,aAAa,CAAC;EACvCe,SAAS,CAACE,OAAO,GAAGjB,aAAa;EAEjC,IAAAkB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACO,SAAS,CAACnB,aAAa,CAAC;EACvC,CAAC,EAAE,CAACA,aAAa,CAAC,CAAC;EAEnB,IAAAkB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACQ,cAAc,CAACnB,WAAW,CAAC;EAC1C,CAAC,EAAE,CAACA,WAAW,CAAC,CAAC;EAEjB,IAAAiB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACS,oBAAoB,CAACnB,iBAAiB,CAAC;EACtD,CAAC,EAAE,CAACA,iBAAiB,CAAC,CAAC;EAEvB,IAAAgB,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAW,iCAAsB,EAACd,UAAU,CAAC;EAC3C,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAY,kCAAuB,EAACb,cAAc,CAAC;EAChD,CAAC,EAAE,CAACC,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,IAAI,CAACR,OAAO,EAAE;IAC7B,MAAMqB,GAAG,GAAG,IAAAC,6BAAkB,EAACpB,UAAU,CAAC;IAC1C;IACA;IACAO,oBAAY,CAACO,SAAS,CAACJ,SAAS,CAACE,OAAO,CAAC;IACzC,OAAOO,GAAG;IACV;EACF,CAAC,EAAE,CAACb,UAAU,EAAER,OAAO,CAAC,CAAC;EAEzB,OAAO;IAAEC,OAAO;IAAEG,OAAO;IAAEE,WAAW;IAAEE,UAAU;IAAEE;EAAY,CAAC;AACnE","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_native","_multiplex","useCompass","options","filterDegrees","smoothingAlpha","declination","pauseOnBackground","enabled","reading","setReading","useState","quality","setQuality","interfering","setInterfering","hasCompass","NitroCompass","diagnostics","getDiagnostics","undefined","permission","setPermission","getPermissionStatus","filterRef","useRef","current","smoothingRef","useEffect","setFilter","setSmoothing","setDeclination","setPauseOnBackground","addCalibrationListener","addInterferenceListener","off","addHeadingListener","e","console","warn","getCurrentHeading","useCallback","recalibrate","setLocation","latitude","longitude","requestPermission","status"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["_native","require","_multiplex","_hook"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,IAAAA,OAAA,GAAAC,OAAA;AAWA,IAAAC,UAAA,GAAAD,OAAA;AAMA,IAAAE,KAAA,GAAAF,OAAA","ignoreList":[]}
1
+ {"version":3,"names":["_native","require","_multiplex","_hook"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,IAAAA,OAAA,GAAAC,OAAA;AAYA,IAAAC,UAAA,GAAAD,OAAA;AAMA,IAAAE,KAAA,GAAAF,OAAA","ignoreList":[]}
@@ -77,6 +77,13 @@ function addHeadingListener(cb) {
77
77
  };
78
78
  }
79
79
 
80
+ // Module-level no-op kept stable so we can swap it back into the native
81
+ // callback slot when the last listener leaves — releasing references
82
+ // to old dispatcher closures, which matters when the JS module is
83
+ // re-evaluated (Metro Fast Refresh, jest module reset).
84
+ const NOOP_CALIBRATION = _ => {};
85
+ const NOOP_INTERFERENCE = _ => {};
86
+
80
87
  /**
81
88
  * Subscribe to calibration-bucket transitions. Only fires while a
82
89
  * heading subscription is active. Returns the unsubscribe function.
@@ -88,7 +95,17 @@ function addCalibrationListener(cb) {
88
95
  }
89
96
  calibrationListeners.add(cb);
90
97
  return () => {
91
- calibrationListeners.delete(cb);
98
+ if (!calibrationListeners.delete(cb)) return;
99
+ if (calibrationListeners.size === 0) {
100
+ // Detach our dispatcher from the native side. Without this, a
101
+ // module reload (Metro Fast Refresh, jest resetModules) leaves
102
+ // the old dispatcher pinned in native memory while a new module
103
+ // load installs a *second* dispatcher pointing at a fresh
104
+ // listener Set — splitting events between the two and silently
105
+ // dropping the now-orphaned listeners.
106
+ _native.NitroCompass.setOnCalibrationNeeded(NOOP_CALIBRATION);
107
+ calibrationRegistered = false;
108
+ }
92
109
  };
93
110
  }
94
111
 
@@ -103,7 +120,11 @@ function addInterferenceListener(cb) {
103
120
  }
104
121
  interferenceListeners.add(cb);
105
122
  return () => {
106
- interferenceListeners.delete(cb);
123
+ if (!interferenceListeners.delete(cb)) return;
124
+ if (interferenceListeners.size === 0) {
125
+ _native.NitroCompass.setOnInterferenceDetected(NOOP_INTERFERENCE);
126
+ interferenceRegistered = false;
127
+ }
107
128
  };
108
129
  }
109
130
  //# sourceMappingURL=multiplex.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_native","require","headingListeners","Set","calibrationListeners","interferenceListeners","calibrationRegistered","interferenceRegistered","DEFAULT_FILTER_DEG","dispatchHeading","sample","cb","Array","from","e","console","error","dispatchCalibration","quality","dispatchInterference","detected","addHeadingListener","wasEmpty","size","add","NitroCompass","start","delete","stop","addCalibrationListener","setOnCalibrationNeeded","addInterferenceListener","setOnInterferenceDetected"],"sourceRoot":"../../src","sources":["multiplex.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_native","require","headingListeners","Set","calibrationListeners","interferenceListeners","calibrationRegistered","interferenceRegistered","DEFAULT_FILTER_DEG","dispatchHeading","sample","cb","Array","from","e","console","error","dispatchCalibration","quality","dispatchInterference","detected","addHeadingListener","wasEmpty","size","add","NitroCompass","start","delete","stop","NOOP_CALIBRATION","_","NOOP_INTERFERENCE","addCalibrationListener","setOnCalibrationNeeded","addInterferenceListener","setOnInterferenceDetected"],"sourceRoot":"../../src","sources":["multiplex.ts"],"mappings":";;;;;;;;AAgBA,IAAAA,OAAA,GAAAC,OAAA;AAhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWA,MAAMC,gBAAgB,GAAG,IAAIC,GAAG,CAAkB,CAAC;AACnD,MAAMC,oBAAoB,GAAG,IAAID,GAAG,CAAsB,CAAC;AAC3D,MAAME,qBAAqB,GAAG,IAAIF,GAAG,CAAuB,CAAC;AAE7D,IAAIG,qBAAqB,GAAG,KAAK;AACjC,IAAIC,sBAAsB,GAAG,KAAK;AAElC,MAAMC,kBAAkB,GAAG,CAAC;AAE5B,SAASC,eAAeA,CAACC,MAAqB,EAAE;EAC9C,KAAK,MAAMC,EAAE,IAAIC,KAAK,CAACC,IAAI,CAACX,gBAAgB,CAAC,EAAE;IAC7C,IAAI;MACFS,EAAE,CAACD,MAAM,CAAC;IACZ,CAAC,CAAC,OAAOI,CAAC,EAAE;MACVC,OAAO,CAACC,KAAK,CAAC,wCAAwC,EAAEF,CAAC,CAAC;IAC5D;EACF;AACF;AAEA,SAASG,mBAAmBA,CAACC,OAAwB,EAAE;EACrD,KAAK,MAAMP,EAAE,IAAIC,KAAK,CAACC,IAAI,CAACT,oBAAoB,CAAC,EAAE;IACjD,IAAI;MACFO,EAAE,CAACO,OAAO,CAAC;IACb,CAAC,CAAC,OAAOJ,CAAC,EAAE;MACVC,OAAO,CAACC,KAAK,CAAC,4CAA4C,EAAEF,CAAC,CAAC;IAChE;EACF;AACF;AAEA,SAASK,oBAAoBA,CAACC,QAAiB,EAAE;EAC/C,KAAK,MAAMT,EAAE,IAAIC,KAAK,CAACC,IAAI,CAACR,qBAAqB,CAAC,EAAE;IAClD,IAAI;MACFM,EAAE,CAACS,QAAQ,CAAC;IACd,CAAC,CAAC,OAAON,CAAC,EAAE;MACVC,OAAO,CAACC,KAAK,CAAC,6CAA6C,EAAEF,CAAC,CAAC;IACjE;EACF;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASO,kBAAkBA,CAACV,EAAmB,EAAc;EAClE,MAAMW,QAAQ,GAAGpB,gBAAgB,CAACqB,IAAI,KAAK,CAAC;EAC5CrB,gBAAgB,CAACsB,GAAG,CAACb,EAAE,CAAC;EACxB,IAAIW,QAAQ,EAAE;IACZG,oBAAY,CAACC,KAAK,CAAClB,kBAAkB,EAAEC,eAAe,CAAC;EACzD;EACA,OAAO,MAAM;IACX,IAAI,CAACP,gBAAgB,CAACyB,MAAM,CAAChB,EAAE,CAAC,EAAE;IAClC,IAAIT,gBAAgB,CAACqB,IAAI,KAAK,CAAC,EAAE;MAC/BE,oBAAY,CAACG,IAAI,CAAC,CAAC;IACrB;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA,MAAMC,gBAAgB,GAAIC,CAAkB,IAAK,CAAC,CAAC;AACnD,MAAMC,iBAAiB,GAAID,CAAU,IAAK,CAAC,CAAC;;AAE5C;AACA;AACA;AACA;AACO,SAASE,sBAAsBA,CACpCrB,EAAuB,EACX;EACZ,IAAI,CAACL,qBAAqB,EAAE;IAC1BmB,oBAAY,CAACQ,sBAAsB,CAAChB,mBAAmB,CAAC;IACxDX,qBAAqB,GAAG,IAAI;EAC9B;EACAF,oBAAoB,CAACoB,GAAG,CAACb,EAAE,CAAC;EAC5B,OAAO,MAAM;IACX,IAAI,CAACP,oBAAoB,CAACuB,MAAM,CAAChB,EAAE,CAAC,EAAE;IACtC,IAAIP,oBAAoB,CAACmB,IAAI,KAAK,CAAC,EAAE;MACnC;MACA;MACA;MACA;MACA;MACA;MACAE,oBAAY,CAACQ,sBAAsB,CAACJ,gBAAgB,CAAC;MACrDvB,qBAAqB,GAAG,KAAK;IAC/B;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACO,SAAS4B,uBAAuBA,CACrCvB,EAAwB,EACZ;EACZ,IAAI,CAACJ,sBAAsB,EAAE;IAC3BkB,oBAAY,CAACU,yBAAyB,CAAChB,oBAAoB,CAAC;IAC5DZ,sBAAsB,GAAG,IAAI;EAC/B;EACAF,qBAAqB,CAACmB,GAAG,CAACb,EAAE,CAAC;EAC7B,OAAO,MAAM;IACX,IAAI,CAACN,qBAAqB,CAACsB,MAAM,CAAChB,EAAE,CAAC,EAAE;IACvC,IAAIN,qBAAqB,CAACkB,IAAI,KAAK,CAAC,EAAE;MACpCE,oBAAY,CAACU,yBAAyB,CAACJ,iBAAiB,CAAC;MACzDxB,sBAAsB,GAAG,KAAK;IAChC;EACF,CAAC;AACH","ignoreList":[]}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
4
  import { NitroCompass } from './native';
5
5
  import { addCalibrationListener, addHeadingListener, addInterferenceListener } from './multiplex';
6
6
  /**
@@ -13,6 +13,7 @@ import { addCalibrationListener, addHeadingListener, addInterferenceListener } f
13
13
  export function useCompass(options = {}) {
14
14
  const {
15
15
  filterDegrees = 1,
16
+ smoothingAlpha = 0.2,
16
17
  declination = 0,
17
18
  pauseOnBackground = true,
18
19
  enabled = true
@@ -20,17 +21,46 @@ export function useCompass(options = {}) {
20
21
  const [reading, setReading] = useState(null);
21
22
  const [quality, setQuality] = useState(null);
22
23
  const [interfering, setInterfering] = useState(false);
23
- const [hasCompass] = useState(() => NitroCompass.hasCompass());
24
- const [diagnostics] = useState(() => NitroCompass.getDiagnostics());
25
24
 
26
- // Tracked via ref so the heading-subscription effect can re-apply
27
- // the user's filter after a stop/start cycle without restarting on
28
- // every filterDegrees change.
25
+ // Wrap in try/catch so a missing/misconfigured native module doesn't
26
+ // throw during render return safe defaults and let the host UI
27
+ // surface "no compass". Without this, the throw bubbles up and
28
+ // becomes a render error that blanks the screen.
29
+ const [hasCompass] = useState(() => {
30
+ try {
31
+ return NitroCompass.hasCompass();
32
+ } catch {
33
+ return false;
34
+ }
35
+ });
36
+ const [diagnostics] = useState(() => {
37
+ try {
38
+ return NitroCompass.getDiagnostics();
39
+ } catch {
40
+ return undefined;
41
+ }
42
+ });
43
+ const [permission, setPermission] = useState(() => {
44
+ try {
45
+ return NitroCompass.getPermissionStatus();
46
+ } catch {
47
+ return 'unknown';
48
+ }
49
+ });
50
+
51
+ // Tracked via refs so the heading-subscription effect can re-apply
52
+ // the user's filter and smoothing after a stop/start cycle without
53
+ // restarting on every option change.
29
54
  const filterRef = useRef(filterDegrees);
30
55
  filterRef.current = filterDegrees;
56
+ const smoothingRef = useRef(smoothingAlpha);
57
+ smoothingRef.current = smoothingAlpha;
31
58
  useEffect(() => {
32
59
  NitroCompass.setFilter(filterDegrees);
33
60
  }, [filterDegrees]);
61
+ useEffect(() => {
62
+ NitroCompass.setSmoothing(smoothingAlpha);
63
+ }, [smoothingAlpha]);
34
64
  useEffect(() => {
35
65
  NitroCompass.setDeclination(declination);
36
66
  }, [declination]);
@@ -46,20 +76,81 @@ export function useCompass(options = {}) {
46
76
  return addInterferenceListener(setInterfering);
47
77
  }, [hasCompass]);
48
78
  useEffect(() => {
49
- if (!hasCompass || !enabled) return;
50
- const off = addHeadingListener(setReading);
51
- // Multiplex starts the sensor with a default filter; re-apply the
52
- // current option after subscribing.
53
- NitroCompass.setFilter(filterRef.current);
79
+ if (!hasCompass || !enabled) {
80
+ // When the user disables the hook, clear stale UI state.
81
+ // Without this, `reading` / `interfering` keep their last value
82
+ // forever, so the consumer's UI can't tell "subscription is off"
83
+ // from "compass is currently quiet".
84
+ setReading(null);
85
+ setInterfering(false);
86
+ return;
87
+ }
88
+ let off;
89
+ try {
90
+ off = addHeadingListener(setReading);
91
+ // Multiplex starts the sensor with a default filter; re-apply
92
+ // the current options after subscribing. recalibrate() can also
93
+ // reset native smoothing state, so we pin our chosen alpha back
94
+ // here as well.
95
+ NitroCompass.setFilter(filterRef.current);
96
+ NitroCompass.setSmoothing(smoothingRef.current);
97
+ } catch (e) {
98
+ // start() throws on iOS when location authorization is denied.
99
+ // Swallow it here so the hook tree doesn't unmount; consumers
100
+ // should call NitroCompass.requestPermission() explicitly before
101
+ // setting enabled=true if they want to recover.
102
+ // eslint-disable-next-line no-console
103
+ console.warn('[NitroCompass] failed to start heading subscription:', e);
104
+ }
54
105
  return off;
55
106
  // eslint-disable-next-line react-hooks/exhaustive-deps
56
107
  }, [hasCompass, enabled]);
108
+
109
+ // Stable callbacks so consumers' useEffect deps don't churn on every
110
+ // render. All four are thin wrappers around NitroCompass; the hook's
111
+ // own state isn't reactive to recalibrate/setLocation since neither
112
+ // changes any field returned here.
113
+ const getCurrentHeading = useCallback(() => {
114
+ try {
115
+ return NitroCompass.getCurrentHeading();
116
+ } catch {
117
+ return undefined;
118
+ }
119
+ }, []);
120
+ const recalibrate = useCallback(() => {
121
+ try {
122
+ NitroCompass.recalibrate();
123
+ } catch {
124
+ // Native missing — nothing to do.
125
+ }
126
+ }, []);
127
+ const setLocation = useCallback((latitude, longitude) => {
128
+ try {
129
+ NitroCompass.setLocation(latitude, longitude);
130
+ } catch {
131
+ // Native missing — silently ignored.
132
+ }
133
+ }, []);
134
+ const requestPermission = useCallback(async () => {
135
+ try {
136
+ const status = await NitroCompass.requestPermission();
137
+ setPermission(status);
138
+ return status;
139
+ } catch {
140
+ return 'denied';
141
+ }
142
+ }, []);
57
143
  return {
58
144
  reading,
59
145
  quality,
60
146
  interfering,
61
147
  hasCompass,
62
- diagnostics
148
+ diagnostics,
149
+ permission,
150
+ getCurrentHeading,
151
+ recalibrate,
152
+ setLocation,
153
+ requestPermission
63
154
  };
64
155
  }
65
156
  //# sourceMappingURL=hook.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","useRef","useState","NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass","options","filterDegrees","declination","pauseOnBackground","enabled","reading","setReading","quality","setQuality","interfering","setInterfering","hasCompass","diagnostics","getDiagnostics","filterRef","current","setFilter","setDeclination","setPauseOnBackground","off"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAMnD,SAASC,YAAY,QAAQ,UAAU;AACvC,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,uBAAuB,QAClB,aAAa;AA2CpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CACxBC,OAA0B,GAAG,CAAC,CAAC,EACb;EAClB,MAAM;IACJC,aAAa,GAAG,CAAC;IACjBC,WAAW,GAAG,CAAC;IACfC,iBAAiB,GAAG,IAAI;IACxBC,OAAO,GAAG;EACZ,CAAC,GAAGJ,OAAO;EAEX,MAAM,CAACK,OAAO,EAAEC,UAAU,CAAC,GAAGZ,QAAQ,CAAuB,IAAI,CAAC;EAClE,MAAM,CAACa,OAAO,EAAEC,UAAU,CAAC,GAAGd,QAAQ,CAAyB,IAAI,CAAC;EACpE,MAAM,CAACe,WAAW,EAAEC,cAAc,CAAC,GAAGhB,QAAQ,CAAC,KAAK,CAAC;EAErD,MAAM,CAACiB,UAAU,CAAC,GAAGjB,QAAQ,CAAC,MAAMC,YAAY,CAACgB,UAAU,CAAC,CAAC,CAAC;EAC9D,MAAM,CAACC,WAAW,CAAC,GAAGlB,QAAQ,CAAC,MAAMC,YAAY,CAACkB,cAAc,CAAC,CAAC,CAAC;;EAEnE;EACA;EACA;EACA,MAAMC,SAAS,GAAGrB,MAAM,CAACQ,aAAa,CAAC;EACvCa,SAAS,CAACC,OAAO,GAAGd,aAAa;EAEjCT,SAAS,CAAC,MAAM;IACdG,YAAY,CAACqB,SAAS,CAACf,aAAa,CAAC;EACvC,CAAC,EAAE,CAACA,aAAa,CAAC,CAAC;EAEnBT,SAAS,CAAC,MAAM;IACdG,YAAY,CAACsB,cAAc,CAACf,WAAW,CAAC;EAC1C,CAAC,EAAE,CAACA,WAAW,CAAC,CAAC;EAEjBV,SAAS,CAAC,MAAM;IACdG,YAAY,CAACuB,oBAAoB,CAACf,iBAAiB,CAAC;EACtD,CAAC,EAAE,CAACA,iBAAiB,CAAC,CAAC;EAEvBX,SAAS,CAAC,MAAM;IACd,IAAI,CAACmB,UAAU,EAAE;IACjB,OAAOf,sBAAsB,CAACY,UAAU,CAAC;EAC3C,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;EAEhBnB,SAAS,CAAC,MAAM;IACd,IAAI,CAACmB,UAAU,EAAE;IACjB,OAAOb,uBAAuB,CAACY,cAAc,CAAC;EAChD,CAAC,EAAE,CAACC,UAAU,CAAC,CAAC;EAEhBnB,SAAS,CAAC,MAAM;IACd,IAAI,CAACmB,UAAU,IAAI,CAACP,OAAO,EAAE;IAC7B,MAAMe,GAAG,GAAGtB,kBAAkB,CAACS,UAAU,CAAC;IAC1C;IACA;IACAX,YAAY,CAACqB,SAAS,CAACF,SAAS,CAACC,OAAO,CAAC;IACzC,OAAOI,GAAG;IACV;EACF,CAAC,EAAE,CAACR,UAAU,EAAEP,OAAO,CAAC,CAAC;EAEzB,OAAO;IAAEC,OAAO;IAAEE,OAAO;IAAEE,WAAW;IAAEE,UAAU;IAAEC;EAAY,CAAC;AACnE","ignoreList":[]}
1
+ {"version":3,"names":["useCallback","useEffect","useRef","useState","NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass","options","filterDegrees","smoothingAlpha","declination","pauseOnBackground","enabled","reading","setReading","quality","setQuality","interfering","setInterfering","hasCompass","diagnostics","getDiagnostics","undefined","permission","setPermission","getPermissionStatus","filterRef","current","smoothingRef","setFilter","setSmoothing","setDeclination","setPauseOnBackground","off","e","console","warn","getCurrentHeading","recalibrate","setLocation","latitude","longitude","requestPermission","status"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AASA,SAASA,YAAY,QAAQ,UAAU;AAWvC,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,uBAAuB,QAClB,aAAa;AAEpB,SAASC,UAAU,QAAQ,QAAQ","ignoreList":[]}
1
+ {"version":3,"names":["NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAUA,SAASA,YAAY,QAAQ,UAAU;AAYvC,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,uBAAuB,QAClB,aAAa;AAEpB,SAASC,UAAU,QAAQ,QAAQ","ignoreList":[]}