diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..8e3124b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Troubleshooting and setup
+ url: https://github.com/su-engineering/react-native-nearby-interaction/blob/main/docs/TROUBLESHOOTING.md
+ about: Check common discovery, configuration, and measurement problems.
+ - name: Report a security vulnerability privately
+ url: https://github.com/su-engineering/react-native-nearby-interaction/security/advisories/new
+ about: Use private reporting instead of a public issue for vulnerabilities.
diff --git a/.github/ISSUE_TEMPLATE/hardware.yml b/.github/ISSUE_TEMPLATE/hardware.yml
new file mode 100644
index 0000000..380593f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/hardware.yml
@@ -0,0 +1,27 @@
+name: Hardware validation
+description: Share reproducible results for an iPhone and UWB accessory combination
+body:
+ - type: markdown
+ attributes:
+ value: "Report observed results, including failures and unavailable readings. Remove device identifiers and raw accessory configuration tokens."
+ - type: textarea
+ id: environment
+ attributes:
+ label: Hardware and software
+ description: Package version or commit, iPhone model, iOS, React Native/Expo, accessory model, and firmware revision (or unknown).
+ validations:
+ required: true
+ - type: textarea
+ id: setup
+ attributes:
+ label: Setup and reproduction
+ description: Example app or minimal reproduction, transport/profile changes, and relevant public protocol documentation.
+ validations:
+ required: true
+ - type: textarea
+ id: results
+ attributes:
+ label: Observed results
+ description: Report distance, angle/direction availability, Stop/Start, foreground resume, tag power-cycle, stale-reading cleanup, and any untested checks. Include approximate distances and conditions without claiming calibrated accuracy.
+ validations:
+ required: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 55be9a7..7810d93 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,6 +4,16 @@ Open an issue before substantial API changes. Bug reports should include package
React Native/Expo, iOS and Xcode versions, tag model/firmware, lifecycle state and
a minimal reproduction. Remove credentials and private accessory tokens.
+Use Node.js 22 for parity with CI. Native checks require macOS and Xcode; CI uses
+Xcode 26.2. Start with the fast checks, then run consumer checks for integration changes.
+
+```sh
+npm ci
+npm run check
+```
+
+Before changing packaging or native integration:
+
```sh
npm ci
npm run release:check
@@ -17,8 +27,24 @@ On macOS, also run `swift test` and compile generated consumers as CI does.
Add tests for changed behavior, update the changelog and document API changes.
Changes to NI lifecycle, BLE negotiation or framing require physical test results
-with hardware/firmware versions. The first beta's physical validation is pending.
+with hardware/firmware versions. See the [first hardware report](docs/validation/2026-09-21.md) for the validated
+configuration and remaining coverage. Report additional hardware using the
+hardware validation issue form.
Preserve explicit session ownership/stop, nullable measurements, main-queue native
isolation and bounded recovery. Keep application-specific behavior out of the
library. Contributions must be yours to submit and distributable under MIT.
+
+## Pull requests
+
+Keep each PR focused on one problem. Explain the observed behavior, the resulting
+behavior, and the checks you ran. Link the relevant issue when there is one.
+For hardware-dependent changes, distinguish measurements observed on a device
+from simulator or mocked tests; record any checks you could not run.
+
+Documentation-only changes need accurate examples and working links. Changes to
+public behavior need appropriate regression coverage. Do not include signing
+profiles, device identifiers, raw nearby-device scans, or private accessory data.
+
+Use respectful, constructive discussion. For vulnerabilities, follow
+[SECURITY.md](SECURITY.md) instead of filing a public bug.
diff --git a/README.md b/README.md
index b237303..d635466 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,34 @@
# React Native Nearby Interaction
-iPhone → UWB accessory ranging through Apple's Nearby Interaction framework.
-This standalone MVP has a configurable Truesense T-TAG BLE profile, an external
-transport API for other accessories, a typed event API, and a React hook.
+**Measure the distance from an iPhone to a UWB accessory in React Native and Expo.**
-Current release: **0.1.0**, published under the `latest` npm tag. Initial physical distance ranging, stop/restart, foreground resume and
-tag reconnection passed on an iPhone 16 Pro with a Truesense T-TAG. See
-[validation](docs/VALIDATION.md) for the evidence and remaining acceptance tests.
+[](https://github.com/su-engineering/react-native-nearby-interaction/actions/workflows/ci.yml)
+[](https://www.npmjs.com/package/@su-engineering/react-native-nearby-interaction)
+[](LICENSE)
+
+Apple's Nearby Interaction framework provides the measurements. This library
+handles the native session, Truesense T-TAG Bluetooth negotiation, and recovery,
+and exposes a typed event API and a React hook.
+
+- **T-TAG support:** configurable BLE services, characteristics, and command bytes.
+- **React state:** distance, session state, accessory details, and errors through one hook.
+- **Session recovery:** bounded retries, foreground resume, and stale-reading cleanup.
+- **Other accessories:** bring your own transport for an Apple-compatible NI protocol.
+- **Native integration:** React Native New Architecture and an Expo config plugin.
+
+Current release: **[0.1.0](https://github.com/su-engineering/react-native-nearby-interaction/releases/tag/v0.1.0)**.
+Distance ranging, stop/restart, foreground resume, and tag reconnection passed on
+an iPhone 16 Pro with a Truesense T-TAG. Direction and angle were unavailable in
+those sessions. See the [hardware report](docs/validation/2026-09-21.md) for the
+exact test scope; other accessories remain unverified.
+
+[Quick start](#installation) · [API reference](docs/API.md) ·
+[Troubleshooting](docs/TROUBLESHOOTING.md) · [Validation](docs/VALIDATION.md) ·
+[Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
## Scope
-| Capability | MVP |
+| Capability | Support in 0.1.0 |
| --- | --- |
| iOS UWB accessory sessions | Implemented |
| Truesense demo firmware handshake | Device-tested with a T-TAG; firmware revision not recorded |
@@ -20,8 +38,8 @@ tag reconnection passed on an iPhone 16 Pro with a Truesense T-TAG. See
| Bounded retries, deadlines, write acknowledgements, stale reading cleanup | Implemented |
| React Native New Architecture | TurboModule + generated event bindings |
| Expo | Info.plist config plugin; native development build required |
-| Android, iPhone-to-iPhone, multiple simultaneous tags | Outside this MVP |
-| Background ranging | Outside this MVP; disconnects and resumes in foreground |
+| Android, iPhone-to-iPhone, multiple simultaneous tags | Outside this release |
+| Background ranging | Outside this release; disconnects and resumes in foreground |
Compatibility target: React Native 0.83.x (baseline 0.83.1), React 19.2.x,
Expo SDK 55 (baseline 55.0.0), iOS 15.1+, Xcode 26.2+ for Expo builds. Other versions are not claimed by this
@@ -36,15 +54,16 @@ Install from npm:
npm install @su-engineering/react-native-nearby-interaction
```
-Before release, install the tarball produced by `npm run pack:check`, or clone
-the repository and install locally:
+For a bare React Native app, install the iOS pods after adding the package:
```sh
-npm install /absolute/path/to/react-native-nearby-interaction
cd ios
bundle exec pod install
```
+You need a UWB-capable iPhone and an Apple-compatible UWB accessory for physical
+ranging. A simulator can validate UI and unsupported-device handling only.
+
Add nonempty `NSNearbyInteractionUsageDescription` and
`NSBluetoothAlwaysUsageDescription` strings to your app's Info.plist. The library
checks their presence before starting a built-in BLE session.
@@ -68,22 +87,54 @@ not add background modes. Expo Go cannot load this native module.
## Basic usage
```tsx
-import {Text} from 'react-native';
+import {useEffect, useState} from 'react';
+import {Button, Text, View} from 'react-native';
import {nearbyInteraction, useNearbyInteraction} from '@su-engineering/react-native-nearby-interaction';
-function Distance() {
+// This screen owns the shared session. Mount only one session owner at a time.
+export function DistanceScreen() {
const {state, measurement, error} = useNearbyInteraction();
- return {error?.message ?? `${state}: ${measurement?.distance ?? '—'} m`};
-}
+ const [busy, setBusy] = useState(false);
+ const [message, setMessage] = useState(null);
+
+ useEffect(() => () => {
+ void nearbyInteraction.stop().catch(console.error);
+ }, []);
+
+ async function run(action: 'start' | 'stop') {
+ setBusy(true);
+ setMessage(null);
+ try {
+ if (action === 'start') {
+ const capabilities = await nearbyInteraction.getCapabilities();
+ if (!capabilities.supported) {
+ setMessage('This device does not support accessory UWB ranging.');
+ return;
+ }
+ await nearbyInteraction.start();
+ } else {
+ await nearbyInteraction.stop();
+ }
+ } catch (cause) {
+ setMessage(cause instanceof Error ? cause.message : String(cause));
+ } finally {
+ setBusy(false);
+ }
+ }
-const capabilities = await nearbyInteraction.getCapabilities();
-if (capabilities.supported) {
- await nearbyInteraction.start({namePrefix: 'T-TAG', maxRetries: 3});
+ return (
+
+ {state}
+ {measurement?.distance == null ? '—' : `${measurement.distance.toFixed(2)} m`}
+ {message ?? error?.message}
+
+ );
}
-await nearbyInteraction.stop();
```
-Omit the name filter if your firmware advertises another name. By default, the
+The example uses the default T-TAG profile without a name filter. By default, the
first matching peripheral is selected. Use `deviceId` (CoreBluetooth UUID) to
target a particular unit. The default scan timeout is 15 seconds.
@@ -210,3 +261,20 @@ and [source lineage](docs/SOURCE.md). This package is MIT licensed; the example
retains the official template's MIT license in `example/TEMPLATE-LICENSE`.
See [contributing](CONTRIBUTING.md), [security](SECURITY.md) and the
[release guide](docs/RELEASING.md).
+
+## Help and contributions
+
+Use the [bug report form](https://github.com/su-engineering/react-native-nearby-interaction/issues/new?template=bug.yml)
+for reproducible problems and the [hardware validation form](https://github.com/su-engineering/react-native-nearby-interaction/issues/new?template=hardware.yml)
+to share results from another device or firmware. Include versions, session states,
+and a minimal reproduction; remove private identifiers and accessory configuration tokens.
+Read the [troubleshooting guide](docs/TROUBLESHOOTING.md) before collecting logs.
+
+Small fixes, regression tests, documentation improvements, and reproducible hardware
+reports are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md). Report security
+issues through the private channel in [SECURITY.md](SECURITY.md).
+
+## License
+
+[MIT](LICENSE), copyright SU Engineering. The example also retains the official
+React Native template's [MIT license](example/TEMPLATE-LICENSE).
diff --git a/SECURITY.md b/SECURITY.md
index 3238a50..520f24c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,9 +1,9 @@
# Security
-Only the latest published beta receives fixes during the pre-1.0 period.
+Only the latest published release receives fixes during the pre-1.0 period.
-Use GitHub's private **Report a vulnerability** feature when available. Otherwise,
-open an issue requesting a private reporting channel without exploit details.
+[Report a vulnerability privately](https://github.com/su-engineering/react-native-nearby-interaction/security/advisories/new)
+through GitHub Security Advisories. Do not include exploit details in public issues.
Include version, affected path, impact and reproduction, with real credentials
and private accessory tokens removed.
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..621cb8c
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,84 @@
+# API reference
+
+Import the shared `nearbyInteraction` client, `useNearbyInteraction` hook,
+`NearbyInteractionError`, and `TTAG_PROFILE` from
+`@su-engineering/react-native-nearby-interaction`. Public interfaces are exported
+as TypeScript types; see [types.ts](../src/types.ts) for their full definitions.
+
+## Session ownership
+
+One client manages one accessory session. Subscribe before calling `start()`.
+Mounting or unmounting the hook does not start or stop the session; the owning
+screen or service must call `stop()` when finished. Stop before replacing an
+active or failed run. See the [complete screen example](../README.md#basic-usage).
+
+| Method | Result and behavior |
+| --- | --- |
+| `getCapabilities()` | `Promise` with `supported`, `preciseDistance`, `direction`, and `platform`. Capability does not guarantee that every measurement contains direction. |
+| `start(options?)` | `Promise`; accepts a run. Observe state and measurement events for ranging progress. |
+| `stop()` | `Promise`; tears down the native session and built-in BLE transport, clears the snapshot, and returns to `idle`. External transport cleanup remains the caller's responsibility. |
+| `configureAccessory(base64, bluetoothPeerId?)` | Supplies Apple accessory configuration during an external session. The optional peer ID has [specific pairing requirements](../README.md#other-accessory-protocols). |
+| `sendData(base64)` | Sends application data through the built-in BLE transport; resolves after write acknowledgements. Stop cancels pending writes. |
+| `on(event, listener)` | Returns a subscription with `remove()`. Listener payloads are typed by event. |
+| `getSnapshot()` | Returns the current read-only snapshot. |
+| `subscribe(listener)` | Observes snapshot changes; returns an unsubscribe function. React applications normally use the hook. |
+
+Methods that accept options or encoded data can throw during validation; wrap
+the call itself in `try`/`catch`, including when using `await`. Native/session
+failures can also reject a returned promise or arrive through the `error` event.
+
+## Start options
+
+| Option | Default | Meaning |
+| --- | --- | --- |
+| `transport` | `'ttag'` | Built-in T-TAG BLE transport or `'external'`. |
+| `deviceId` | Unset | Target CoreBluetooth peripheral UUID, not a MAC address. |
+| `namePrefix` | Unset | Restrict discovery by advertised name prefix. Leave unset if the firmware name is unknown. |
+| `profile` | `TTAG_PROFILE` | BLE UUIDs, command bytes, and optional chunking. See [profile configuration](../README.md#configure-the-accessory-profile). |
+| `maxRetries` | `3` | Additional attempts per run; integer `0–10`. |
+| `timeoutMs` | `15000` | Scan, connection, and handshake deadline; integer `1000–120000`. |
+| `updateIntervalMs` | `100` | Native measurement delivery throttle; integer `0–5000`. |
+| `staleAfterMs` | `2000` | Clear the snapshot reading after this many milliseconds without an update; integer `100–30000`, greater than `updateIntervalMs`. |
+
+Base64 arguments must be nonempty canonical base64 and decode to at most 64 KiB.
+BLE payload limits can be smaller; chunking requires explicit firmware support.
+
+## Hook and measurements
+
+`useNearbyInteraction()` returns `{state, accessory, measurement, error}`.
+Accessory, measurement, and error are nullable. A measurement contains:
+
+| Field | Unit / shape |
+| --- | --- |
+| `distance` | Metres, or `null`. |
+| `horizontalAngle` | Radians, or `null`. |
+| `direction` | Apple's direction vector `{x, y, z}`, or `null`. |
+| `timestamp` | Unix timestamp in milliseconds. |
+
+Use a placeholder for unavailable data; do not convert `null` to zero. Distance,
+angle, and direction can be unavailable independently. Direction and angle were
+not available during the initial T-TAG hardware validation.
+
+The snapshot clears measurements on stop, disconnection, non-ranging states,
+and stale timeout. Applications maintaining their own event history must apply
+their own display and retention policy.
+
+## Events
+
+| Event | Payload |
+| --- | --- |
+| `state` | `{state}`: `idle`, `waitingForBluetooth`, `scanning`, `connecting`, `configuring`, `ranging`, `suspended`, `reconnecting`, or `failed`. |
+| `connected` | `{id, name}`. |
+| `disconnected` | `{reason}`. |
+| `measurement` | The measurement described above. |
+| `configuration` | `{data}`: base64 Apple shareable configuration to deliver using an external transport. |
+| `data` | `{data, source}`: opaque base64 data; source is `'nus'` or `'configuration'`. |
+| `error` | `{code, message, recoverable}`. |
+
+Remove event subscriptions when their owner is disposed. External transports
+must renegotiate when the session returns to `configuring`, including after
+retry or foreground resume. A recoverable error can be followed by recovery;
+`failed` requires correcting the cause, stopping, and starting again.
+
+See [troubleshooting](TROUBLESHOOTING.md) for common failure stages and
+[architecture](ARCHITECTURE.md) for the native/JavaScript boundary.
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md
new file mode 100644
index 0000000..ee60936
--- /dev/null
+++ b/docs/TROUBLESHOOTING.md
@@ -0,0 +1,35 @@
+# Troubleshooting
+
+Start with the [example app](../example/App.tsx), a UWB-capable iPhone, and a
+powered-on compatible accessory. Allow Bluetooth and Nearby Interaction access.
+Record the package, iOS, React Native/Expo, and firmware versions before comparing
+results with the [validated hardware configuration](validation/2026-09-21.md).
+
+| Symptom | What to check |
+| --- | --- |
+| Native module unavailable | Rebuild the native app after installing the package. Install CocoaPods dependencies in a bare app. Expo Go cannot load this module; use a native development build. |
+| Device reports unsupported | Physical UWB requires compatible iPhone hardware. The simulator cannot range. Check `getCapabilities()` before starting. |
+| Waiting for Bluetooth | Enable Bluetooth and check the app's Bluetooth permission in Settings. |
+| Scanning without finding a tag | Remove `namePrefix` and `deviceId` while diagnosing discovery. Firmware names vary. Check the service UUID, close other apps connected to the tag, and power-cycle it. |
+| Connecting/configuring repeatedly | Check service/characteristic UUIDs and firmware framing. The built-in profile requires write-with-response and notifications/indications. A discovered BLE device is not necessarily NI-compatible. |
+| Invalid accessory configuration | Use the accessory's Apple NI configuration bytes, not an iPhone discovery token. With an external transport, omit the optional peer UUID unless the pairing and standard NI GATT requirements in the README are met. |
+| `SESSION_ACTIVE` | Call `stop()` before starting a new run, including after failure. Use one owner for the shared session. |
+| `RETRY_LIMIT` or `failed` | Inspect the preceding error, correct the cause, then Stop / reset and Start. Retrying indefinitely will not fix an incompatible profile or denied permission. |
+| State is ranging but the distance is blank | Check the measurement event and nullable distance separately. A reading can become unavailable or expire. If the event contains a distance, inspect your UI colors and formatting; the example supports light and dark appearances. |
+| No angle or direction | These values can be `null` independently of distance. They were unavailable in the initial hardware tests; do not substitute zero. |
+| Reading disappears in the background | Background ranging is outside this release. Foreground resume reconnects and configures again. External transports must handle fresh negotiation. |
+| Oversized BLE payload | Keep messages within the supported write size. Enable chunking only if the firmware reassembles the complete command. |
+
+## Collect a useful report
+
+1. Reproduce in the example with one tag, then record the sequence of states and
+ the exact error code/message. The example includes a bounded session log.
+2. Record the smallest relevant configuration: transport, profile changes,
+ timeouts, and whether a device/name filter was used.
+3. State whether Stop → Start, foreground resume, or tag power-cycle changes
+ the result. Distinguish a missing UI value from an absent measurement event.
+4. Submit a [bug report](https://github.com/su-engineering/react-native-nearby-interaction/issues/new?template=bug.yml)
+ with a minimal reproduction. Remove device identifiers, nearby-device scans,
+ credentials, and raw Apple accessory configuration/shareable-data tokens.
+
+Use [private vulnerability reporting](../SECURITY.md) for security issues.