spotny-sdk
v1.0.25
Published
Beacon Scanner
Readme
spotny-sdk
A React Native SDK for real-time iBeacon proximity experiences. Detects nearby beacons, fetches campaigns, and fires tracking events automatically on iOS and Android — all tied to your authenticated user.
Requires React Native 0.73+ with the New Architecture (Turbo Modules) enabled.
Installation
npm install spotny-sdk
# or
yarn add spotny-sdkiOS
cd ios && pod installAdd the following keys to your Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to detect nearby points of interest.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We use your location in the background to detect nearby points of interest.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We use Bluetooth to detect nearby points of interest.</string>Enable Background Modes in Xcode → your app target → Signing & Capabilities → Background Modes:
- ✅ Location updates
- ✅ Uses Bluetooth LE accessories
Android
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />Quick Start
import { useEffect } from 'react';
import {
initialize,
startScanner,
stopScanner,
addBeaconsRangedListener,
addBeaconRegionListener,
} from 'spotny-sdk';
export default function App() {
useEffect(() => {
// 1. Initialize once — call after the user is logged in
initialize({
token: 'YOUR_SDK_TOKEN', // from your Spotny dashboard
apiKey: 'your-brand-key', // identifies your app/brand
identifierId: currentUser.id, // your authenticated user ID
maxDetectionDistance: 10,
});
// 2. Listen for nearby beacons
const beaconSub = addBeaconsRangedListener(({ beacons, region }) => {
beacons.forEach((b) =>
console.log(
`${b.major}/${b.minor} — ${b.distance.toFixed(1)}m (${b.proximity})`
)
);
});
// 3. Listen for region enter/exit
const regionSub = addBeaconRegionListener(({ event, region }) => {
console.log(`${event.toUpperCase()}: ${region}`);
});
// 4. Start scanning
startScanner();
return () => {
stopScanner();
beaconSub.remove();
regionSub.remove();
};
}, []);
return null;
}API Reference
Initialization
initialize(config)
Must be called before any other SDK function. Authenticates with the Spotny backend and obtains a session JWT that is automatically used for all subsequent API calls.
| Option | Type | Default | Description |
| -------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| token | string | required | SDK token issued by Spotny for your app. Verified against the backend — rejects with UNAUTHORIZED if invalid. |
| apiKey | string | required | Identifies your brand or app (e.g. 'nike'). Sent to the backend during verification. |
| identifierId | string | required | Unique identifier for the authenticated user (e.g. database UUID, hashed email). Embedded in the session JWT so all tracking events are tied to this user. |
| maxDetectionDistance | number | 8.0 | Maximum detection radius in metres. Beacons beyond this are ignored. |
| distanceCorrectionFactor | number | 0.5 | Multiplier applied to raw RSSI distance. Tune for your beacon TX power. |
Returns Promise<string>. Rejects if any required field is missing, the token is invalid, or the network request fails — handle the error before calling startScanner().
Note: The SDK is designed for authenticated (logged-in) users. Call
initialize()after the user has signed in so theiridentifierIdis available.
await initialize({
token: 'YOUR_SDK_TOKEN',
apiKey: 'nike',
identifierId: currentUser.id,
maxDetectionDistance: 10,
distanceCorrectionFactor: 0.5,
});JWT persistence & auto-refresh
The session JWT returned by initialize() is persisted to Keychain (iOS) / SharedPreferences (Android) and automatically restored when the app relaunches — so scanning resumes without calling initialize() again after a force-quit or crash.
If the JWT expires mid-session, the SDK transparently re-fetches it before the next API call. No action is required from your app.
Core Scanning
startScanner()
Starts iBeacon scanning. The SDK manages a persistent device_id internally (Keychain on iOS, SharedPreferences on Android — survives reinstalls).
Returns Promise<string>.
await startScanner();stopScanner()
Stops scanning and cleans up all per-beacon state (sends PROXIMITY_EXIT events for any active beacons).
Returns Promise<string>.
isScanning()
Returns Promise<boolean> — true if the scanner is currently active.
Permissions
requestNotificationPermissions() (iOS only)
Prompts the user for local notification permission (alert, sound, badge).
Returns Promise<'granted' | 'denied'>.
Event Listeners
addBeaconsRangedListener(callback)
Fires when the set of nearby beacons changes, or every debounceInterval seconds (default 5 s) if nothing changed. The event is deduplicated — it does not fire every ranging cycle if proximity is unchanged.
const sub = addBeaconsRangedListener(({ beacons, region }) => {
beacons.forEach((b) => {
console.log(`Major ${b.major} / Minor ${b.minor}`);
console.log(`Distance: ${b.distance.toFixed(2)}m — ${b.proximity}`);
console.log(`RSSI: ${b.rssi} dBm`);
});
});
sub.remove(); // call on cleanupEach beacon in the array:
| Field | Type | Description |
| ----------- | -------- | -------------------------------------------------------------- |
| uuid | string | Beacon proximity UUID |
| major | number | Beacon major value |
| minor | number | Beacon minor value |
| distance | number | Estimated distance in metres (after correction factor applied) |
| rssi | number | Raw signal strength in dBm |
| proximity | string | 'immediate' | 'near' | 'far' | 'unknown' |
addBeaconRegionListener(callback)
Fires when the user enters or exits a beacon region. Works in foreground, background, and terminated state (iOS).
const sub = addBeaconRegionListener(({ event, region, state }) => {
if (event === 'enter') console.log('Entered region', region);
if (event === 'exit') console.log('Left region', region);
if (event === 'determined') console.log('State for', region, '→', state);
});
sub.remove(); // call on cleanup| Field | Type | Description |
| -------- | -------- | ------------------------------------------------------------------ |
| event | string | 'enter' | 'exit' | 'determined' |
| region | string | Region identifier |
| state | string | 'inside' | 'outside' | 'unknown' (determined event only) |
Debug Helpers
getDebugLogs()
Returns the on-device debug log file as a string. Includes campaign fetch results and proximity events sent.
Returns Promise<string>.
clearDebugLogs()
Clears the on-device debug log file.
Returns Promise<string>.
setDebounceInterval(seconds)
Sets how often the onBeaconsRanged event is force-emitted even if proximity hasn't changed (default: 5).
await setDebounceInterval(10); // emit at most every 10 sclearDebounceCache()
Resets internal timing state. Useful during testing.
getDebounceStatus()
Returns internal per-beacon timing state. Useful for debugging.
Returns Promise<Object>.
How It Works
initialize()sends yourtoken,apiKey, andidentifierIdto the Spotny backend, which issues a session JWT. The JWT is persisted locally and auto-refreshed when it expires.startScanner()begins monitoring for iBeacons with the Spotny UUID.- When a beacon is first detected, the SDK immediately sends a
NEARBYproximity event with thebeacon_idto log the user's presence. - When the user gets within 3 meters, the SDK calls
/distributewith thebeacon_idto fetch campaign data (campaign_id,session_id,inQueueflag). - The SDK stores this campaign data locally.
- Additional
NEARBYevents are sent automatically as the user moves closer or further (when distance changes > 0.75m). When a campaign is active, these events include thecampaign_id. IMPRESSION_HEARTBEATevents are sent every 10 s when the user is within 2 m of a beacon with an active campaign (not queued). These events includecampaign_idandsession_id.- On exit,
PROXIMITY_EXITis sent and all state is cleaned up.
Simplified tracking: All events send beacon_id + optional campaign_id when available. The backend resolves beacon → screen mapping, allowing flexible beacon reassignment without SDK updates.
All events are tied to the authenticated identifierId supplied during initialize() — no additional identity calls are required.
Platform Support
| Feature | iOS | Android | | ------------------------ | --- | ------- | | iBeacon ranging | ✅ | ✅ | | Region enter/exit | ✅ | ✅ | | Background scanning | ✅ | ✅ | | Terminated-state wakeup | ✅ | ✅ | | JWT persistence | ✅ | ✅ | | Keychain device ID | ✅ | – | | Notification permissions | ✅ | – |
Contributing
License
MIT
