{"_id":"@blunt-ly/ble-advertiser","_rev":"2-fbc0720089b97c28300e0f2c325cb1f8","name":"@blunt-ly/ble-advertiser","dist-tags":{"latest":"0.1.0"},"versions":{"0.0.0":{"name":"@blunt-ly/ble-advertiser","version":"0.0.0","keywords":["react-native","expo","bluetooth","ble","advertiser","ble-advertiser"],"author":{"name":"The Blunt.ly Authors"},"license":"MIT","_id":"@blunt-ly/ble-advertiser@0.0.0","maintainers":[{"name":"snehil-shah","email":"snehilshah.989@gmail.com"}],"homepage":"https://github.com/Blunt-ly/ble-advertiser#readme","bugs":{"url":"https://github.com/Blunt-ly/ble-advertiser/issues"},"dist":{"shasum":"dd2bba6f827eca0ab5ef63ef455913b4e59adedb","tarball":"https://registry.npmjs.org/@blunt-ly/ble-advertiser/-/ble-advertiser-0.0.0.tgz","fileCount":29,"integrity":"sha512-25X+WENupb6hnr5EcQk5RqZVJYusRgNu8iimG8L7urLxf7cGoPJR1BhQbn84QEvzwdXSwKr4Ibbp71UzKUho2Q==","signatures":[{"sig":"MEQCIEjEmCAwlj/lrZ4e4V2mAkj6s2ntHOdS7/p96uiwNPK3AiAErFvZ0oeehEWGRYno/EGG7DheCVnCXLRc1/6av1Merw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51303},"main":"build/index.js","types":"build/index.d.ts","gitHead":"8f08b2cf4c8de6814de10c5aa122c475006644df","scripts":{"lint":"eslint src/","build":"node internal/module_scripts/build.js","clean":"node internal/module_scripts/clean.js","prepare":"node internal/module_scripts/prepare.js","open:ios":"node internal/module_scripts/open-ios.js","open:android":"node internal/module_scripts/open-android.js"},"_npmUser":{"name":"snehil-shah","email":"snehilshah.989@gmail.com"},"repository":{"url":"git+https://github.com/Blunt-ly/ble-advertiser.git","type":"git"},"_npmVersion":"11.6.2","description":"Minimal BLE advertiser for React-Native, optimized for background processing.","directories":{},"_nodeVersion":"24.12.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"expo":"^56.0.3","eslint":"~9.39.4","typescript":"^5.9.2","@types/react":"~19.1.1","react-native":"0.82.1","eslint-config-universe":"^15.0.3"},"peerDependencies":{"expo":"*","react":"*","react-native":"*"},"_npmOperationalInternal":{"tmp":"tmp/ble-advertiser_0.0.0_1779565814251_0.3808028150580327","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@blunt-ly/ble-advertiser","version":"0.1.0","description":"Minimal BLE advertiser for React-Native, optimized for background processing.","main":"build/index.js","types":"build/index.d.ts","scripts":{"build":"node internal/module_scripts/build.js","clean":"node internal/module_scripts/clean.js","lint":"eslint src/","prepare":"node internal/module_scripts/prepare.js","open:ios":"node internal/module_scripts/open-ios.js","open:android":"node internal/module_scripts/open-android.js"},"keywords":["react-native","expo","bluetooth","ble","advertiser","ble-advertiser"],"repository":{"type":"git","url":"git+https://github.com/Blunt-ly/ble-advertiser.git"},"bugs":{"url":"https://github.com/Blunt-ly/ble-advertiser/issues"},"author":{"name":"The Blunt.ly Authors"},"license":"MIT","homepage":"https://github.com/Blunt-ly/ble-advertiser#readme","dependencies":{},"devDependencies":{"@types/react":"~19.1.1","eslint":"~9.39.4","eslint-config-universe":"^15.0.3","expo":"^56.0.3","react-native":"0.82.1","typescript":"^5.9.2"},"peerDependencies":{"expo":"*","react":"*","react-native":"*"},"gitHead":"8f08b2cf4c8de6814de10c5aa122c475006644df","_id":"@blunt-ly/ble-advertiser@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-G5Y0PBzCJ0CUCwnEGrdfpqjkskX6sPltht3kbwm7+ZadIoSzy/Vqk7Wy6PLFhO7eLjmTpEFv9VZ1bXXhSDfLFA==","shasum":"cd493f22dbce41b3a840197ddd2c5eddcec25bf6","tarball":"https://registry.npmjs.org/@blunt-ly/ble-advertiser/-/ble-advertiser-0.1.0.tgz","fileCount":29,"unpackedSize":51303,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@blunt-ly%2fble-advertiser@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDHmiD9Q0DfOSmwGb8WkCHQThluU3ihr5lNj7LFU+nwoAIgeqaj/bPCAkZNAd2PFFeky9pwC0ZW2RUj8SSInZbm+VA="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:d648e16e-11c3-4907-872d-9dd0efcfe5dd"}},"directories":{},"maintainers":[{"name":"snehil-shah","email":"snehilshah.989@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ble-advertiser_0.1.0_1779566034888_0.14853471514672756"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-23T19:50:14.147Z","modified":"2026-05-23T19:53:55.290Z","0.0.0":"2026-05-23T19:50:14.388Z","0.1.0":"2026-05-23T19:53:55.037Z"},"bugs":{"url":"https://github.com/Blunt-ly/ble-advertiser/issues"},"author":{"name":"The Blunt.ly Authors"},"license":"MIT","homepage":"https://github.com/Blunt-ly/ble-advertiser#readme","keywords":["react-native","expo","bluetooth","ble","advertiser","ble-advertiser"],"repository":{"type":"git","url":"git+https://github.com/Blunt-ly/ble-advertiser.git"},"description":"Minimal BLE advertiser for React-Native, optimized for background processing.","maintainers":[{"name":"snehil-shah","email":"snehilshah.989@gmail.com"}],"readme":"# BLE Advertiser Module\n\nA cross-platform BLE (Bluetooth Low Energy) advertiser module for React Native using Expo Modules API. This module allows you to advertise service UUIDs over BLE on both Android and iOS platforms, using minimal platform features to support background processing.\n\n## Installation\n\n```bash\nnpm install @blunt-ly/ble-advertiser\n```\n\n## API\n\n### Methods\n\n#### `broadcast(uuids: string[]): Promise<string>`\n\nStart BLE advertising with the specified service UUIDs.\n\n```typescript\nimport * as BleAdvertiser from '@blunt-ly/ble-advertiser';\n\nconst serviceUUIDs = [\n  '6ba7b810-9dad-11d1-80b4-00c04fd430c8',\n  '180D' // 16-bit UUIDs are also supported\n];\n\ntry {\n  const result = await BleAdvertiser.broadcast(serviceUUIDs);\n  console.log('Advertising started:', result);\n} catch (error) {\n  console.error('Failed to start advertising:', error);\n}\n```\n\n> [!NOTE]\n> When an iOS app advertises in the background, CoreBluetooth moves its service UUIDs out of the standard advertisement field and into an \"overflow area\" that is only visible to scanners which already know the UUIDs in advance. To work around this, the module exposes a GATT characteristic that holds the device-specific UUIDs, so a scanner that recognizes a shared \"network\" UUID in the overflow area can connect via GATT to retrieve them. See [Scanning in iOS background mode](#scanning-in-ios-background-mode) for the consumer-side pattern.\n\n#### `stopBroadcast(): Promise<{stopped: boolean, stoppedAdvertisers?: number}>`\n\nStop all BLE advertising.\n\n```typescript\ntry {\n  const result = await BleAdvertiser.stopBroadcast();\n  console.log('Advertising stopped:', result);\n} catch (error) {\n  console.error('Failed to stop advertising:', error);\n}\n```\n\n#### `isSupported(): Promise<boolean>`\n\nCheck if BLE advertising is supported on the current device.\n\n```typescript\nconst supported = await BleAdvertiser.isSupported();\nconsole.log('BLE advertising supported:', supported);\n```\n\n#### `isEnabled(): Promise<boolean>`\n\nCheck if Bluetooth is currently enabled.\n\n```typescript\nconst enabled = await BleAdvertiser.isEnabled();\nconsole.log('Bluetooth enabled:', enabled);\n```\n\n### Events\n\n#### `onAdvertisingStarted`\n\nFired when advertising starts successfully.\n\n```typescript\nimport { addAdvertisingStartedListener } from '@blunt-ly/ble-advertiser';\n\nconst subscription = addAdvertisingStartedListener((event) => {\n  console.log('Advertising started with UUIDs:', event.uuids);\n});\n\n// Don't forget to remove the listener\nsubscription.remove();\n```\n\n#### `onAdvertisingFailed`\n\nFired when advertising fails to start.\n\n```typescript\nimport { addAdvertisingFailedListener } from '@blunt-ly/ble-advertiser';\n\nconst subscription = addAdvertisingFailedListener((event) => {\n  console.log('Advertising failed:', event.error);\n  if (event.errorCode) {\n    console.log('Error code:', event.errorCode);\n  }\n});\n```\n\n#### `onAdvertisingStopped`\n\nFired when advertising is stopped.\n\n```typescript\nimport { addAdvertisingStoppedListener } from '@blunt-ly/ble-advertiser';\n\nconst subscription = addAdvertisingStoppedListener((event) => {\n  console.log('Advertising stopped');\n  if (event.uuids) {\n    console.log('UUIDs that were being advertised:', event.uuids);\n  }\n});\n```\n\n## Error Handling\n\nThe module provides comprehensive error handling with specific error codes and messages:\n\n- `BLUETOOTH_NOT_AVAILABLE` - Bluetooth adapter not available\n- `BLUETOOTH_DISABLED` - Bluetooth is turned off\n- `ADVERTISER_NOT_AVAILABLE` - BLE advertiser not supported\n- `INVALID_UUID` - Invalid UUID format provided\n- `NO_UUIDS` - No service UUIDs provided\n- `ADVERTISING_FAILED` - Platform-specific advertising failure\n\n## Example Usage\n\n```typescript\nimport React, { useEffect, useState } from 'react';\nimport { View, Button, Text, Alert } from 'react-native';\nimport * as BleAdvertiser from '@blunt-ly/ble-advertiser';\n\nexport default function BleAdvertiserExample() {\n  const [isAdvertising, setIsAdvertising] = useState(false);\n  const [isSupported, setIsSupported] = useState(false);\n\n  useEffect(() => {\n    // Check if BLE advertising is supported\n    BleAdvertiser.isSupported().then(setIsSupported);\n\n    // Set up event listeners\n    const startedSubscription = BleAdvertiser.addAdvertisingStartedListener((event) => {\n      setIsAdvertising(true);\n      Alert.alert('Advertising Started', `UUIDs: ${event.uuids.join(', ')}`);\n    });\n\n    const failedSubscription = BleAdvertiser.addAdvertisingFailedListener((event) => {\n      setIsAdvertising(false);\n      Alert.alert('Advertising Failed', event.error);\n    });\n\n    const stoppedSubscription = BleAdvertiser.addAdvertisingStoppedListener(() => {\n      setIsAdvertising(false);\n      Alert.alert('Advertising Stopped');\n    });\n\n    return () => {\n      startedSubscription.remove();\n      failedSubscription.remove();\n      stoppedSubscription.remove();\n    };\n  }, []);\n\n  const startAdvertising = async () => {\n    try {\n      const serviceUUIDs = ['6ba7b810-9dad-11d1-80b4-00c04fd430c8'];\n      await BleAdvertiser.broadcast(serviceUUIDs);\n    } catch (error) {\n      Alert.alert('Error', error.message);\n    }\n  };\n\n  const stopAdvertising = async () => {\n    try {\n      await BleAdvertiser.stopBroadcast();\n    } catch (error) {\n      Alert.alert('Error', error.message);\n    }\n  };\n\n  if (!isSupported) {\n    return (\n      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n        <Text>BLE Advertising is not supported on this device</Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n      <Text>BLE Advertising Status: {isAdvertising ? 'Active' : 'Inactive'}</Text>\n      <Button\n        title={isAdvertising ? 'Stop Advertising' : 'Start Advertising'}\n        onPress={isAdvertising ? stopAdvertising : startAdvertising}\n      />\n    </View>\n  );\n}\n```\n\n## Scanning in iOS background mode\n\nWhen the advertising app is backgrounded on iOS, its device-specific service UUIDs only surface in the scanner's `overflowServiceUUIDs` field — and only if the scanner is already scanning for a known \"network\" UUID that the advertiser also broadcasts. The pattern is:\n\n1. The advertiser broadcasts a shared `networkId` UUID plus a device-specific UUID.\n2. The scanner filters on `networkId`.\n3. In the foreground both UUIDs are visible in `advertising.serviceUUIDs`.\n4. In the background only `networkId` is visible (via `overflowServiceUUIDs`); the scanner then connects via GATT to read the device-specific UUID from the characteristic exposed by this module.\n\n```typescript\nconst NETWORK_ID = '<your-shared-network-uuid>';\nconst inFlightConnections = new Set<string>();\n\nawait scanner.start(async (peripheral) => {\n  let targetBroadcastId: string | undefined | null;\n\n  // Normal path: both service UUIDs are visible in the advertisement\n  if (peripheral.advertising.serviceUUIDs?.length === 2) {\n    targetBroadcastId = peripheral.advertising.serviceUUIDs.find(\n      (uuid) => uuid.toLowerCase() !== NETWORK_ID.toLowerCase()\n    );\n  }\n\n  // Overflow path: only the known network UUID is visible via the overflow area.\n  // Connect via GATT to read the device-specific broadcast ID.\n  if (!targetBroadcastId) {\n    const overflowUUIDs = peripheral.advertising.overflowServiceUUIDs as string[] | undefined;\n    const hasNetworkInOverflow = overflowUUIDs?.some(\n      (uuid) => uuid.toLowerCase() === NETWORK_ID.toLowerCase()\n    );\n    if (!hasNetworkInOverflow) return;\n\n    if (inFlightConnections.has(peripheral.id)) return;\n    inFlightConnections.add(peripheral.id);\n\n    targetBroadcastId = await readBroadcastIdViaGatt(peripheral.id, NETWORK_ID);\n\n    // Allow re-connection after a cooldown\n    setTimeout(() => inFlightConnections.delete(peripheral.id), 60_000);\n  }\n\n  if (!targetBroadcastId) return;\n\n  // Handle the resolved broadcast ID...\n}, [NETWORK_ID]);\n```\n","readmeFilename":"README.md"}