npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-sdk

iOS

cd ios && pod install

Add 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 their identifierId is 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 cleanup

Each 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 s

clearDebounceCache()

Resets internal timing state. Useful during testing.

getDebounceStatus()

Returns internal per-beacon timing state. Useful for debugging.

Returns Promise<Object>.


How It Works

  1. initialize() sends your token, apiKey, and identifierId to the Spotny backend, which issues a session JWT. The JWT is persisted locally and auto-refreshed when it expires.
  2. startScanner() begins monitoring for iBeacons with the Spotny UUID.
  3. When a beacon is first detected, the SDK immediately sends a NEARBY proximity event with the beacon_id to log the user's presence.
  4. When the user gets within 3 meters, the SDK calls /distribute with the beacon_id to fetch campaign data (campaign_id, session_id, inQueue flag).
  5. The SDK stores this campaign data locally.
  6. Additional NEARBY events are sent automatically as the user moves closer or further (when distance changes > 0.75m). When a campaign is active, these events include the campaign_id.
  7. IMPRESSION_HEARTBEAT events are sent every 10 s when the user is within 2 m of a beacon with an active campaign (not queued). These events include campaign_id and session_id.
  8. On exit, PROXIMITY_EXIT is 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