react-native-nitro-compass 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/NitroCompass.podspec +31 -0
  3. package/README.md +206 -0
  4. package/android/CMakeLists.txt +32 -0
  5. package/android/build.gradle +148 -0
  6. package/android/fix-prefab.gradle +51 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +9 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrocompass/HybridNitroCompass.kt +481 -0
  11. package/android/src/main/java/com/margelo/nitro/nitrocompass/NitroCompassPackage.kt +18 -0
  12. package/app.plugin.js +16 -0
  13. package/ios/Bridge.h +8 -0
  14. package/ios/HybridNitroCompass.swift +473 -0
  15. package/lib/commonjs/hook.js +69 -0
  16. package/lib/commonjs/hook.js.map +1 -0
  17. package/lib/commonjs/index.js +39 -0
  18. package/lib/commonjs/index.js.map +1 -0
  19. package/lib/commonjs/multiplex.js +109 -0
  20. package/lib/commonjs/multiplex.js.map +1 -0
  21. package/lib/commonjs/native.js +9 -0
  22. package/lib/commonjs/native.js.map +1 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/commonjs/specs/NitroCompass.nitro.js +6 -0
  25. package/lib/commonjs/specs/NitroCompass.nitro.js.map +1 -0
  26. package/lib/module/hook.js +65 -0
  27. package/lib/module/hook.js.map +1 -0
  28. package/lib/module/index.js +6 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/multiplex.js +103 -0
  31. package/lib/module/multiplex.js.map +1 -0
  32. package/lib/module/native.js +5 -0
  33. package/lib/module/native.js.map +1 -0
  34. package/lib/module/specs/NitroCompass.nitro.js +4 -0
  35. package/lib/module/specs/NitroCompass.nitro.js.map +1 -0
  36. package/lib/typescript/src/hook.d.ts +49 -0
  37. package/lib/typescript/src/hook.d.ts.map +1 -0
  38. package/lib/typescript/src/index.d.ts +8 -0
  39. package/lib/typescript/src/index.d.ts.map +1 -0
  40. package/lib/typescript/src/multiplex.d.ts +38 -0
  41. package/lib/typescript/src/multiplex.d.ts.map +1 -0
  42. package/lib/typescript/src/native.d.ts +3 -0
  43. package/lib/typescript/src/native.d.ts.map +1 -0
  44. package/lib/typescript/src/specs/NitroCompass.nitro.d.ts +176 -0
  45. package/lib/typescript/src/specs/NitroCompass.nitro.d.ts.map +1 -0
  46. package/nitro.json +30 -0
  47. package/nitrogen/generated/.gitattributes +1 -0
  48. package/nitrogen/generated/android/NitroCompass+autolinking.cmake +81 -0
  49. package/nitrogen/generated/android/NitroCompass+autolinking.gradle +27 -0
  50. package/nitrogen/generated/android/NitroCompassOnLoad.cpp +60 -0
  51. package/nitrogen/generated/android/NitroCompassOnLoad.hpp +34 -0
  52. package/nitrogen/generated/android/c++/JAccuracyQuality.hpp +64 -0
  53. package/nitrogen/generated/android/c++/JCompassSample.hpp +61 -0
  54. package/nitrogen/generated/android/c++/JFunc_void_AccuracyQuality.hpp +77 -0
  55. package/nitrogen/generated/android/c++/JFunc_void_CompassSample.hpp +77 -0
  56. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  57. package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.cpp +143 -0
  58. package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.hpp +75 -0
  59. package/nitrogen/generated/android/c++/JPermissionStatus.hpp +61 -0
  60. package/nitrogen/generated/android/c++/JSensorDiagnostics.hpp +58 -0
  61. package/nitrogen/generated/android/c++/JSensorKind.hpp +61 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/AccuracyQuality.kt +25 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/CompassSample.kt +56 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_AccuracyQuality.kt +80 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_CompassSample.kt +80 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_bool.kt +80 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/HybridNitroCompassSpec.kt +118 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/NitroCompassOnLoad.kt +35 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/PermissionStatus.kt +24 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/SensorDiagnostics.kt +51 -0
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/SensorKind.kt +24 -0
  72. package/nitrogen/generated/ios/NitroCompass+autolinking.rb +62 -0
  73. package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Bridge.cpp +73 -0
  74. package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Bridge.hpp +267 -0
  75. package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Umbrella.hpp +61 -0
  76. package/nitrogen/generated/ios/NitroCompassAutolinking.mm +33 -0
  77. package/nitrogen/generated/ios/NitroCompassAutolinking.swift +26 -0
  78. package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.cpp +11 -0
  79. package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.hpp +180 -0
  80. package/nitrogen/generated/ios/swift/AccuracyQuality.swift +48 -0
  81. package/nitrogen/generated/ios/swift/CompassSample.swift +34 -0
  82. package/nitrogen/generated/ios/swift/Func_void_AccuracyQuality.swift +46 -0
  83. package/nitrogen/generated/ios/swift/Func_void_CompassSample.swift +46 -0
  84. package/nitrogen/generated/ios/swift/Func_void_PermissionStatus.swift +46 -0
  85. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  86. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  87. package/nitrogen/generated/ios/swift/HybridNitroCompassSpec.swift +67 -0
  88. package/nitrogen/generated/ios/swift/HybridNitroCompassSpec_cxx.swift +309 -0
  89. package/nitrogen/generated/ios/swift/PermissionStatus.swift +44 -0
  90. package/nitrogen/generated/ios/swift/SensorDiagnostics.swift +29 -0
  91. package/nitrogen/generated/ios/swift/SensorKind.swift +44 -0
  92. package/nitrogen/generated/shared/c++/AccuracyQuality.hpp +84 -0
  93. package/nitrogen/generated/shared/c++/CompassSample.hpp +87 -0
  94. package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.cpp +33 -0
  95. package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.hpp +87 -0
  96. package/nitrogen/generated/shared/c++/PermissionStatus.hpp +80 -0
  97. package/nitrogen/generated/shared/c++/SensorDiagnostics.hpp +84 -0
  98. package/nitrogen/generated/shared/c++/SensorKind.hpp +80 -0
  99. package/package.json +136 -0
  100. package/react-native.config.js +11 -0
  101. package/src/hook.ts +118 -0
  102. package/src/index.ts +28 -0
  103. package/src/multiplex.ts +117 -0
  104. package/src/native.ts +5 -0
  105. package/src/specs/NitroCompass.nitro.ts +193 -0
package/package.json ADDED
@@ -0,0 +1,136 @@
1
+ {
2
+ "name": "react-native-nitro-compass",
3
+ "version": "0.1.0",
4
+ "engines": {
5
+ "node": ">=18"
6
+ },
7
+ "description": "Fast, accurate compass heading for React Native, powered by Nitro Modules. Uses Android TYPE_ROTATION_VECTOR sensor fusion and iOS CLHeading.",
8
+ "main": "./lib/commonjs/index.js",
9
+ "module": "./lib/module/index.js",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "react-native": "src/index",
12
+ "source": "src/index",
13
+ "scripts": {
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "jest",
16
+ "clean": "git clean -dfX",
17
+ "release": "semantic-release",
18
+ "build": "npm run typecheck && bob build",
19
+ "prepare": "bob build",
20
+ "codegen": "nitrogen --logLevel=\"debug\" && npm run build"
21
+ },
22
+ "keywords": [
23
+ "react-native",
24
+ "nitro",
25
+ "nitro-modules",
26
+ "compass",
27
+ "heading",
28
+ "magnetometer",
29
+ "qibla",
30
+ "rotation-vector",
31
+ "ios",
32
+ "android"
33
+ ],
34
+ "files": [
35
+ "src",
36
+ "!src/__tests__",
37
+ "react-native.config.js",
38
+ "lib",
39
+ "!lib/typescript/src/__tests__",
40
+ "nitrogen",
41
+ "cpp",
42
+ "nitro.json",
43
+ "android/build.gradle",
44
+ "android/fix-prefab.gradle",
45
+ "android/gradle.properties",
46
+ "android/CMakeLists.txt",
47
+ "android/src",
48
+ "ios/**/*.h",
49
+ "ios/**/*.m",
50
+ "ios/**/*.mm",
51
+ "ios/**/*.cpp",
52
+ "ios/**/*.swift",
53
+ "app.plugin.js",
54
+ "*.podspec",
55
+ "README.md"
56
+ ],
57
+ "repository": "https://github.com/omarsdev/react-native-nitro-compass.git",
58
+ "author": "Omar Sukarieh",
59
+ "license": "MIT",
60
+ "bugs": "https://github.com/omarsdev/react-native-nitro-compass/issues",
61
+ "homepage": "https://github.com/omarsdev/react-native-nitro-compass#readme",
62
+ "publishConfig": {
63
+ "access": "public",
64
+ "registry": "https://registry.npmjs.org/"
65
+ },
66
+ "devDependencies": {
67
+ "@jamesacarr/eslint-formatter-github-actions": "^0.2.0",
68
+ "@semantic-release/changelog": "^6.0.3",
69
+ "@semantic-release/git": "^10.0.1",
70
+ "@types/jest": "^29.5.12",
71
+ "@types/react": "19.2.0",
72
+ "babel-jest": "^30.3.0",
73
+ "conventional-changelog-conventionalcommits": "^9.1.0",
74
+ "jest": "^30.3.0",
75
+ "nitrogen": "0.35.6",
76
+ "react": "19.2.3",
77
+ "react-native": "0.84.1",
78
+ "react-native-builder-bob": "^0.40.18",
79
+ "react-native-nitro-modules": "0.35.6",
80
+ "semantic-release": "^25.0.3",
81
+ "typescript": "^5.8.3"
82
+ },
83
+ "peerDependencies": {
84
+ "react": "*",
85
+ "react-native": "*",
86
+ "react-native-nitro-modules": "*"
87
+ },
88
+ "eslintConfig": {
89
+ "root": true,
90
+ "extends": [
91
+ "@react-native",
92
+ "prettier"
93
+ ],
94
+ "plugins": [
95
+ "prettier"
96
+ ],
97
+ "rules": {
98
+ "prettier/prettier": [
99
+ "warn",
100
+ {
101
+ "quoteProps": "consistent",
102
+ "singleQuote": true,
103
+ "tabWidth": 2,
104
+ "trailingComma": "es5",
105
+ "useTabs": false
106
+ }
107
+ ]
108
+ }
109
+ },
110
+ "eslintIgnore": [
111
+ "node_modules/",
112
+ "lib/"
113
+ ],
114
+ "prettier": {
115
+ "quoteProps": "consistent",
116
+ "singleQuote": true,
117
+ "tabWidth": 2,
118
+ "trailingComma": "es5",
119
+ "useTabs": false,
120
+ "semi": false
121
+ },
122
+ "react-native-builder-bob": {
123
+ "source": "src",
124
+ "output": "lib",
125
+ "targets": [
126
+ "commonjs",
127
+ "module",
128
+ [
129
+ "typescript",
130
+ {
131
+ "project": "tsconfig.json"
132
+ }
133
+ ]
134
+ ]
135
+ }
136
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ packageImportPath:
6
+ 'import com.margelo.nitro.nitrocompass.NitroCompassPackage;',
7
+ packageInstance: 'new NitroCompassPackage()',
8
+ },
9
+ },
10
+ },
11
+ }
package/src/hook.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import type {
3
+ AccuracyQuality,
4
+ CompassSample,
5
+ SensorDiagnostics,
6
+ } from './specs/NitroCompass.nitro'
7
+ import { NitroCompass } from './native'
8
+ import {
9
+ addCalibrationListener,
10
+ addHeadingListener,
11
+ addInterferenceListener,
12
+ } from './multiplex'
13
+
14
+ export interface UseCompassOptions {
15
+ /**
16
+ * Minimum change between samples in degrees. Pass `0` for "every
17
+ * event". Default `1`. Updated live via `NitroCompass.setFilter()`.
18
+ * Note: this is global state shared with any other consumer of
19
+ * the library — last-write-wins.
20
+ */
21
+ filterDegrees?: number
22
+ /**
23
+ * Magnetic-to-true offset in signed degrees. Default `0` (magnetic).
24
+ * Pull from a model like `geomagnetism` keyed on the user's lat/lon.
25
+ * Like `filterDegrees`, this is shared global state.
26
+ */
27
+ declination?: number
28
+ /**
29
+ * Pause the underlying sensor when the app is backgrounded. Default
30
+ * `true`. Shared global state.
31
+ */
32
+ pauseOnBackground?: boolean
33
+ /**
34
+ * Toggle the heading subscription without unmounting the hook.
35
+ * Default `true`. When `false`, the hook still observes calibration
36
+ * and interference (so you can show warnings) but won't tear down
37
+ * its own state — `reading` simply stops updating.
38
+ */
39
+ enabled?: boolean
40
+ }
41
+
42
+ export interface UseCompassResult {
43
+ /** Latest emitted sample, or `null` until the first arrives. */
44
+ reading: CompassSample | null
45
+ /** Coarse accuracy bucket, or `null` until the first transition. */
46
+ quality: AccuracyQuality | null
47
+ /** Whether external magnetic interference is currently detected. */
48
+ interfering: boolean
49
+ /** Hardware availability — read once on first render. */
50
+ hasCompass: boolean
51
+ /** Which sensor backs the readings on this device. */
52
+ diagnostics: SensorDiagnostics | undefined
53
+ }
54
+
55
+ /**
56
+ * Ergonomic React wrapper for the NitroCompass surface. Handles
57
+ * subscription lifecycle, callback registration, and the live-tuneable
58
+ * knobs. Multiple instances mounted at once safely share the same
59
+ * underlying native subscription via the multi-listener primitives in
60
+ * `./multiplex`.
61
+ */
62
+ export function useCompass(
63
+ options: UseCompassOptions = {}
64
+ ): UseCompassResult {
65
+ const {
66
+ filterDegrees = 1,
67
+ declination = 0,
68
+ pauseOnBackground = true,
69
+ enabled = true,
70
+ } = options
71
+
72
+ const [reading, setReading] = useState<CompassSample | null>(null)
73
+ const [quality, setQuality] = useState<AccuracyQuality | null>(null)
74
+ const [interfering, setInterfering] = useState(false)
75
+
76
+ const [hasCompass] = useState(() => NitroCompass.hasCompass())
77
+ const [diagnostics] = useState(() => NitroCompass.getDiagnostics())
78
+
79
+ // Tracked via ref so the heading-subscription effect can re-apply
80
+ // the user's filter after a stop/start cycle without restarting on
81
+ // every filterDegrees change.
82
+ const filterRef = useRef(filterDegrees)
83
+ filterRef.current = filterDegrees
84
+
85
+ useEffect(() => {
86
+ NitroCompass.setFilter(filterDegrees)
87
+ }, [filterDegrees])
88
+
89
+ useEffect(() => {
90
+ NitroCompass.setDeclination(declination)
91
+ }, [declination])
92
+
93
+ useEffect(() => {
94
+ NitroCompass.setPauseOnBackground(pauseOnBackground)
95
+ }, [pauseOnBackground])
96
+
97
+ useEffect(() => {
98
+ if (!hasCompass) return
99
+ return addCalibrationListener(setQuality)
100
+ }, [hasCompass])
101
+
102
+ useEffect(() => {
103
+ if (!hasCompass) return
104
+ return addInterferenceListener(setInterfering)
105
+ }, [hasCompass])
106
+
107
+ useEffect(() => {
108
+ if (!hasCompass || !enabled) return
109
+ const off = addHeadingListener(setReading)
110
+ // Multiplex starts the sensor with a default filter; re-apply the
111
+ // current option after subscribing.
112
+ NitroCompass.setFilter(filterRef.current)
113
+ return off
114
+ // eslint-disable-next-line react-hooks/exhaustive-deps
115
+ }, [hasCompass, enabled])
116
+
117
+ return { reading, quality, interfering, hasCompass, diagnostics }
118
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type {
2
+ AccuracyQuality,
3
+ CompassSample,
4
+ NitroCompass as NitroCompassSpec,
5
+ PermissionStatus,
6
+ SensorDiagnostics,
7
+ SensorKind,
8
+ } from './specs/NitroCompass.nitro'
9
+
10
+ export { NitroCompass } from './native'
11
+
12
+ export type {
13
+ AccuracyQuality,
14
+ CompassSample,
15
+ PermissionStatus,
16
+ SensorDiagnostics,
17
+ SensorKind,
18
+ }
19
+ export type { NitroCompassSpec as NitroCompassHybridObject }
20
+
21
+ export {
22
+ addCalibrationListener,
23
+ addHeadingListener,
24
+ addInterferenceListener,
25
+ } from './multiplex'
26
+
27
+ export { useCompass } from './hook'
28
+ export type { UseCompassOptions, UseCompassResult } from './hook'
@@ -0,0 +1,117 @@
1
+ /**
2
+ * JS-side fan-out so multiple consumers can subscribe to the same
3
+ * compass stream without clobbering each other. The native API is
4
+ * single-callback by design (start, setOnCalibrationNeeded,
5
+ * setOnInterferenceDetected each own one slot); these helpers wrap
6
+ * that into multi-listener primitives with reference-counted
7
+ * lifecycle.
8
+ *
9
+ * Mixing direct `NitroCompass.start()` / `setOnCalibrationNeeded()` /
10
+ * `setOnInterferenceDetected()` calls with these helpers will
11
+ * clobber the multiplex's internal callback slot — pick one path.
12
+ */
13
+ import type {
14
+ AccuracyQuality,
15
+ CompassSample,
16
+ } from './specs/NitroCompass.nitro'
17
+ import { NitroCompass } from './native'
18
+
19
+ type HeadingListener = (sample: CompassSample) => void
20
+ type CalibrationListener = (quality: AccuracyQuality) => void
21
+ type InterferenceListener = (interferenceDetected: boolean) => void
22
+
23
+ const headingListeners = new Set<HeadingListener>()
24
+ const calibrationListeners = new Set<CalibrationListener>()
25
+ const interferenceListeners = new Set<InterferenceListener>()
26
+
27
+ let calibrationRegistered = false
28
+ let interferenceRegistered = false
29
+
30
+ const DEFAULT_FILTER_DEG = 1
31
+
32
+ function dispatchHeading(sample: CompassSample) {
33
+ for (const cb of Array.from(headingListeners)) {
34
+ try {
35
+ cb(sample)
36
+ } catch (e) {
37
+ console.error('[NitroCompass] heading listener threw:', e)
38
+ }
39
+ }
40
+ }
41
+
42
+ function dispatchCalibration(quality: AccuracyQuality) {
43
+ for (const cb of Array.from(calibrationListeners)) {
44
+ try {
45
+ cb(quality)
46
+ } catch (e) {
47
+ console.error('[NitroCompass] calibration listener threw:', e)
48
+ }
49
+ }
50
+ }
51
+
52
+ function dispatchInterference(detected: boolean) {
53
+ for (const cb of Array.from(interferenceListeners)) {
54
+ try {
55
+ cb(detected)
56
+ } catch (e) {
57
+ console.error('[NitroCompass] interference listener threw:', e)
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Subscribe to heading samples. The first listener implicitly calls
64
+ * `NitroCompass.start()`; the last `unsubscribe()` calls
65
+ * `NitroCompass.stop()`. Returns the unsubscribe function.
66
+ *
67
+ * Filter, declination, and pauseOnBackground remain global state on
68
+ * `NitroCompass` and are shared across all listeners — call
69
+ * `NitroCompass.setFilter()` etc. directly to tune them.
70
+ */
71
+ export function addHeadingListener(cb: HeadingListener): () => void {
72
+ const wasEmpty = headingListeners.size === 0
73
+ headingListeners.add(cb)
74
+ if (wasEmpty) {
75
+ NitroCompass.start(DEFAULT_FILTER_DEG, dispatchHeading)
76
+ }
77
+ return () => {
78
+ if (!headingListeners.delete(cb)) return
79
+ if (headingListeners.size === 0) {
80
+ NitroCompass.stop()
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Subscribe to calibration-bucket transitions. Only fires while a
87
+ * heading subscription is active. Returns the unsubscribe function.
88
+ */
89
+ export function addCalibrationListener(
90
+ cb: CalibrationListener
91
+ ): () => void {
92
+ if (!calibrationRegistered) {
93
+ NitroCompass.setOnCalibrationNeeded(dispatchCalibration)
94
+ calibrationRegistered = true
95
+ }
96
+ calibrationListeners.add(cb)
97
+ return () => {
98
+ calibrationListeners.delete(cb)
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Subscribe to magnetic-interference transitions. Only fires while a
104
+ * heading subscription is active. Returns the unsubscribe function.
105
+ */
106
+ export function addInterferenceListener(
107
+ cb: InterferenceListener
108
+ ): () => void {
109
+ if (!interferenceRegistered) {
110
+ NitroCompass.setOnInterferenceDetected(dispatchInterference)
111
+ interferenceRegistered = true
112
+ }
113
+ interferenceListeners.add(cb)
114
+ return () => {
115
+ interferenceListeners.delete(cb)
116
+ }
117
+ }
package/src/native.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { NitroModules } from 'react-native-nitro-modules'
2
+ import type { NitroCompass as NitroCompassSpec } from './specs/NitroCompass.nitro'
3
+
4
+ export const NitroCompass =
5
+ NitroModules.createHybridObject<NitroCompassSpec>('NitroCompass')
@@ -0,0 +1,193 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules'
2
+
3
+ /**
4
+ * One compass heading sample, delivered to the JS callback registered with
5
+ * `start()`. Both fields are in degrees.
6
+ *
7
+ * - `heading`: heading clockwise from north, in `[0, 360)`. Magnetic by
8
+ * default; if you call `setDeclination(deg)` the offset is applied
9
+ * natively before this sample is delivered (and reflected in
10
+ * `getCurrentHeading()`).
11
+ * - `accuracy`: estimated heading uncertainty in degrees, or `-1` when the
12
+ * platform has not yet reported a usable accuracy. Smaller is better.
13
+ * On Android this is read from `event.values[4]` of the rotation-vector
14
+ * sensor when available, otherwise mapped from `SensorManager.SENSOR_STATUS_*`.
15
+ * On iOS this is `CLHeading.headingAccuracy`.
16
+ */
17
+ export interface CompassSample {
18
+ heading: number
19
+ accuracy: number
20
+ }
21
+
22
+ /**
23
+ * Coarse calibration bucket reported via `setOnCalibrationNeeded`. Buckets
24
+ * are derived from numeric heading accuracy on both platforms (same
25
+ * thresholds), so values agree across iOS and Android:
26
+ *
27
+ * `<5°` → `high`, `<15°` → `medium`, `<30°` → `low`, otherwise `unreliable`.
28
+ *
29
+ * On iOS `unreliable` is also reported when the system asks to display
30
+ * its built-in calibration UI (we suppress it).
31
+ */
32
+ export type AccuracyQuality = 'high' | 'medium' | 'low' | 'unreliable'
33
+
34
+ /**
35
+ * Identifies which underlying sensor / framework is producing headings.
36
+ *
37
+ * - `rotationVector` — Android `Sensor.TYPE_ROTATION_VECTOR` (gyro + accel
38
+ * + magnetometer fused). Best quality.
39
+ * - `geomagneticRotationVector` — Android
40
+ * `Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR` (accel + magnetometer only).
41
+ * Used as fallback on gyroless / budget devices; lower update rate and
42
+ * more susceptible to magnetic interference.
43
+ * - `coreLocation` — iOS `CLLocationManager` heading. Apple's stack
44
+ * handles fusion natively.
45
+ */
46
+ export type SensorKind =
47
+ | 'rotationVector'
48
+ | 'geomagneticRotationVector'
49
+ | 'coreLocation'
50
+
51
+ export interface SensorDiagnostics {
52
+ sensor: SensorKind
53
+ }
54
+
55
+ /**
56
+ * Platform permission state required to deliver headings.
57
+ *
58
+ * - `granted` — headings will deliver. iOS: `authorizedAlways` /
59
+ * `authorizedWhenInUse`. Android: always (sensors require no permission).
60
+ * - `denied` — user has refused or the OS has restricted access (e.g.
61
+ * parental controls). On iOS, `start()` will throw with this status.
62
+ * - `unknown` — iOS `notDetermined`: nothing has been asked yet. Calling
63
+ * `requestPermission()` is the way to resolve from `unknown` to
64
+ * `granted`/`denied`.
65
+ */
66
+ export type PermissionStatus = 'granted' | 'denied' | 'unknown'
67
+
68
+ /**
69
+ * Native compass module. Pull this from `NitroModules.createHybridObject`
70
+ * via the bundled `NitroCompass` export, e.g.:
71
+ *
72
+ * ```ts
73
+ * import { NitroCompass } from 'react-native-nitro-compass'
74
+ *
75
+ * NitroCompass.start(1, ({ heading, accuracy }) => {
76
+ * console.log(heading, accuracy)
77
+ * })
78
+ * NitroCompass.stop()
79
+ * ```
80
+ */
81
+ export interface NitroCompass extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
82
+ /**
83
+ * Begin emitting heading samples to `onHeading`. If `start()` has already
84
+ * been called without a matching `stop()`, the previous subscription is
85
+ * silently torn down and replaced — the old `onHeading` is detached.
86
+ *
87
+ * @param filterDegrees Minimum change (in degrees) between samples before
88
+ * the next one is emitted. Pass `0` for "every event"; typical UI values
89
+ * are 1–3. Use `setFilter()` to update without restarting.
90
+ * @param onHeading JS callback invoked on each accepted sample. Called on
91
+ * the JS thread; calling `stop()` from inside this callback is safe.
92
+ */
93
+ start(filterDegrees: number, onHeading: (sample: CompassSample) => void): void
94
+
95
+ /** Stop the underlying sensor / location-manager subscription. Idempotent — safe to call when not started. */
96
+ stop(): void
97
+
98
+ /** Whether `start()` has been called without a matching `stop()`. */
99
+ isStarted(): boolean
100
+
101
+ /**
102
+ * Update the deadband filter live without tearing down the subscription.
103
+ * Same semantics as the `filterDegrees` argument to `start()`. Has no
104
+ * effect until `start()` is called.
105
+ */
106
+ setFilter(degrees: number): void
107
+
108
+ /**
109
+ * Describe which underlying sensor / framework would produce headings on
110
+ * this device. Returns `undefined` if the device has no compass hardware
111
+ * (equivalent to `hasCompass() === false`). Safe to call before `start()`.
112
+ */
113
+ getDiagnostics(): SensorDiagnostics | undefined
114
+
115
+ /**
116
+ * Whether the device has the hardware required for a compass reading.
117
+ * Android: a rotation-vector sensor (fused or geomagnetic) is present.
118
+ * iOS: `CLLocationManager.headingAvailable()`.
119
+ */
120
+ hasCompass(): boolean
121
+
122
+ /**
123
+ * Last sample emitted by the active subscription, with declination
124
+ * already applied. Returns `undefined` when not started, or when started
125
+ * but no sample has arrived yet.
126
+ */
127
+ getCurrentHeading(): CompassSample | undefined
128
+
129
+ /**
130
+ * Magnetic-to-true offset (degrees, signed) added to every heading
131
+ * before it leaves the native side. Pass `0` to revert to magnetic.
132
+ * Survives across `start`/`stop`. Apply your own declination from a
133
+ * model like `geomagnetism` keyed on the user's lat/lon.
134
+ */
135
+ setDeclination(degrees: number): void
136
+
137
+ /**
138
+ * Register a callback fired when the calibration bucket transitions.
139
+ * Replaces any previously registered callback. Pass a no-op to mute.
140
+ * The callback is invoked on the JS thread.
141
+ */
142
+ setOnCalibrationNeeded(onChange: (quality: AccuracyQuality) => void): void
143
+
144
+ /**
145
+ * Register a callback fired when external magnetic interference is
146
+ * detected — typical sources are laptops, monitors, car engines, and
147
+ * large steel structures, all of which can skew heading by tens of
148
+ * degrees while the calibration bucket still reads `'medium'` or
149
+ * better. Fires `true` when the raw magnetic field magnitude leaves
150
+ * the normal Earth band (~20–70 µT) and `false` when it returns.
151
+ * Only transitions are reported; the callback is debounce-free, so
152
+ * brief excursions still fire.
153
+ *
154
+ * Replaces any previously registered callback. Pass a no-op to mute.
155
+ * The callback is invoked on the JS thread. Only meaningful while
156
+ * `start()` is active.
157
+ *
158
+ * iOS uses `CMDeviceMotion.magneticField` — the calibrated field
159
+ * with the device's own hard-iron bias subtracted in real time.
160
+ * No transitions are reported until CoreMotion's bias estimate
161
+ * converges (a second or two of normal device movement); that's
162
+ * required, otherwise raw readings dominated by internal bias
163
+ * would fire false alarms continuously.
164
+ */
165
+ setOnInterferenceDetected(onChange: (interferenceDetected: boolean) => void): void
166
+
167
+ /**
168
+ * Toggle automatic pause/resume on app background/foreground. Default
169
+ * `true`. When enabled, the underlying sensor / location-manager
170
+ * subscription is silently paused while the app is backgrounded and
171
+ * resumed when it returns to the foreground; the JS callback and
172
+ * declination are preserved across the pause. Call before or after
173
+ * `start()`; takes effect immediately.
174
+ */
175
+ setPauseOnBackground(enabled: boolean): void
176
+
177
+ /**
178
+ * Read the current platform permission state synchronously.
179
+ * On Android this is always `'granted'` (sensors require no permission);
180
+ * on iOS it maps `CLLocationManager.authorizationStatus`.
181
+ */
182
+ getPermissionStatus(): PermissionStatus
183
+
184
+ /**
185
+ * Request the platform permission required to deliver headings. On iOS
186
+ * this prompts the system "Allow location" dialog if the status is
187
+ * `'unknown'` and resolves once the user makes a choice. If already
188
+ * `'granted'` or `'denied'` it resolves immediately with that value
189
+ * (iOS does not re-prompt). On Android it resolves immediately with
190
+ * `'granted'`.
191
+ */
192
+ requestPermission(): Promise<PermissionStatus>
193
+ }