Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ class BleDidcommModule(private val context: ReactApplicationContext) :
}
}

@ReactMethod
fun shutdownCentral(
promise: Promise
) {
try {
this.centralManager?.shutdownCentral()
this.centralManager = null
promise.resolve(null)
} catch (e: Exception) {
promise.reject("error", e)
}
}

@ReactMethod
fun shutdownPeripheral(
promise: Promise
) {
try {
this.peripheralManager?.shutdownPeripheral()
this.peripheralManager = null
promise.resolve(null)
} catch (e: Exception) {
promise.reject("error", e)
}
}

@ReactMethod
fun setCentralService(
serviceUUID: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ class CentralManager(private val context: ReactContext) {
this.indicationCharacteristicUUID = indicationCharacteristicUUID
}

fun shutdownCentral() {
try {
this.stopScan()
this.connectedPeripheral?.disconnect()
this.connectedPeripheral?.close()
} catch (e: CentralManagerException.NotScanning) {
// Not Scanning
} catch (e: Exception) {
// Error we don't care about
} finally {
this.serviceUUID = null
this.writeCharacteristicUUID = null
this.indicationCharacteristicUUID = null
this.connectedPeripheral = null
this.discoveredPeripherals.clear()
}
}

@RequiresPermission(value = "android.permission.BLUETOOTH_SCAN")
fun scan(scanCallback: ScanCallback) {
if (this.scanCallback !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ class PeripheralManager(
gattServer.addService(this.service)
}

fun shutdownPeripheral() {
this.gattServer.connectedDevices.clear()
this.gattServer.close()
try {
this.stopAdvertising()
} catch (e: Exception) {
// Do nothing
}
this.writeCharacteristic = null
this.connectedClient = null
this.indicationCharacteristic = null
this.service = null
}

@RequiresPermission(value = "android.permission.BLUETOOTH_ADVERTISE")
fun advertise(advertiseCallback: AdvertiseCallback) {
val service =
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 7e2e1772ef7b97168c880eeaf3749d8c145ffd6e
React-jsinspector: 0553c9fe7218e1f127be070bd5a4d2fc19fb8190
React-logger: cffcc09e8aba8a3014be8d18da7f922802e9f19e
react-native-ble-didcomm: c69fc0a192219416d8ff791b1130b4ecbfb731a0
react-native-ble-didcomm: aaf5cf8b7ef1e946f6b2fcdcd0d07bcd936e4419
React-perflogger: 082b4293f0b3914ff41da35a6c06ac4490fcbcc8
React-RCTActionSheet: 83da3030deb5dea54b398129f56540a44e64d3ae
React-RCTAnimation: bac3a4f4c0436554d9f7fbb1352a0cdcb1fb0f1c
Expand Down
8 changes: 8 additions & 0 deletions ios/BleDidcomm.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ @interface RCT_EXTERN_MODULE(BleDidcomm, RCTEventEmitter)
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(shutdownCentral
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(startPeripheral
:(NSDictionary *)options
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(shutdownPeripheral
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(setPeripheralService
:(NSString *)serviceUUID
writeCharacteristicUUID:(NSString *)writeCharacteristicUUID
Expand Down
22 changes: 22 additions & 0 deletions ios/BleDidcomm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ class BleDidcomm: React.RCTEventEmitter {
resolve(nil)
}

@objc func shutdownPeripheral(
resolve: RCTPromiseResolveBlock,
reject _: RCTPromiseRejectBlock
) {
guard let peripheralManager = self.peripheralManager else {
return
}
peripheralManager.shutdownPeripheral()
resolve(nil)
}

@objc func setPeripheralService(
_ serviceUUID: String,
writeCharacteristicUUID: String,
Expand Down Expand Up @@ -67,6 +78,17 @@ class BleDidcomm: React.RCTEventEmitter {
resolve(nil)
}

@objc func shutdownCentral(
resolve: RCTPromiseResolveBlock,
reject: RCTPromiseRejectBlock
) {
guard let centralManager = self.centralManager else {
return
}
centralManager.shutdownCentral()
resolve(nil)
}

@objc func advertise(
_: [String: String],
resolve: RCTPromiseResolveBlock,
Expand Down
20 changes: 20 additions & 0 deletions ios/CentralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ class CentralManager: NSObject {
)
}

func shutdownCentral() {
if let cp = self.connectedPeripheral {
do {
try self.centralManager.cancelPeripheralConnection(cp)
} catch {
// We don't care and proceed
}
}
if (self.centralManager.isScanning) {
self.stopScan()
}
self.peripherals = []
self.writeCharacteristic = nil
self.connectedPeripheral = nil
self.receivedMessage = nil
self.serviceUUID = nil
self.writeCharacteristicUUID = nil
self.indicationCharacteristicUUID = nil
}

func setService(
serviceUUID: String,
writeCharacteristicUUID: String,
Expand Down
21 changes: 21 additions & 0 deletions ios/PeripheralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ class PeripheralManager: NSObject {
while !isPoweredOn { Thread.sleep(forTimeInterval: 0.05) }
}

func shutdownPeripheral() {
if (self.peripheralManager.isAdvertising) {
do {
try self.stopAdvertising()
} catch {
// we don't care and proceed
}
}
self.service = nil
self.writeCharacteristic = nil
self.indicationCharacteristic = nil
self.peripheralManager.removeAllServices()
}

func setService(
serviceUUID: String, writeCharacteristicUUID: String, indicationCharacteristicUUID: String
) throws {
Expand Down Expand Up @@ -61,6 +75,13 @@ class PeripheralManager: NSObject {
self.peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [service.uuid]])
}

func stopAdvertising() throws {
guard let service = self.service else {
throw PeripheralManagerError.NoDefinedService
}
self.peripheralManager.stopAdvertising()
}

func indicate(message: Data) throws {
guard let connectedCentral = connectedCentral else {
throw PeripheralManagerError.NotConnectedToCentral
Expand Down
54 changes: 54 additions & 0 deletions src/CentralProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Central } from './central'
import type { PropsWithChildren } from 'react'

import { useContext, useEffect, useState } from 'react'
import * as React from 'react'

interface CentralContextInterface {
loading: boolean
central?: Central | undefined
}

interface CentralProps {
central: Central | undefined
}

export const CentralContext = React.createContext<
CentralContextInterface | undefined
>(undefined)

export const useCentral = () => {
const peripheralContext = useContext(CentralContext)
if (!peripheralContext) {
throw new Error('useAgent must be used within a AgentContextProvider')
}
return peripheralContext
}

const PeripheralProvider: React.FC<PropsWithChildren<CentralProps>> = ({
central,
children,
}) => {
const [centralState, setCentralState] = useState<CentralContextInterface>({
loading: true,
central: central,
})

const setInitialState = async () => {
if (central) {
setCentralState({ central: central, loading: false })
}
}

useEffect(() => {
setInitialState()
}, [centralState])

return (
<CentralContext.Provider value={centralState}>
{children}
</CentralContext.Provider>
)
}

export default PeripheralProvider
56 changes: 56 additions & 0 deletions src/PeripheralProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { PropsWithChildren } from 'react'
import type { Peripheral } from './peripheral'

import { useContext, useEffect, useState } from 'react'
import * as React from 'react'

interface PeripheralContextInterface {
loading: boolean
peripheral?: Peripheral | undefined
}

interface PeripheralProps {
peripheral: Peripheral | undefined
}

export const PeripheralContext = React.createContext<
PeripheralContextInterface | undefined
>(undefined)

export const usePeripheral = () => {
const peripheralContext = useContext(PeripheralContext)
if (!peripheralContext) {
throw new Error('useAgent must be used within a AgentContextProvider')
}
return peripheralContext
}

const PeripheralProvider: React.FC<PropsWithChildren<PeripheralProps>> = ({
peripheral,
children,
}) => {
const [peripheralState, setPeripheralState] = useState<
PeripheralContextInterface
>({
loading: true,
peripheral: peripheral,
})

const setInitialState = async () => {
if (peripheral) {
setPeripheralState({ peripheral: peripheral, loading: false })
}
}

useEffect(() => {
setInitialState()
}, [peripheralState])

return (
<PeripheralContext.Provider value={peripheralState}>
{children}
</PeripheralContext.Provider>
)
}

export default PeripheralProvider
16 changes: 16 additions & 0 deletions src/ble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@ export type ServiceOptions = {
indicationUUID: string
}

export type BleState = {
isRunning: boolean
isScanning: boolean
isAdvertising: boolean
isConnected: boolean
}

export const initialState: BleState = {
isRunning: false,
isScanning: false,
isAdvertising: false,
isConnected: false,
}

export interface Ble {
state: BleState
sendMessage(message: string): Promise<void>
start(): Promise<void>
setService(options: ServiceOptions): Promise<void>
getState(): BleState
shutdown(): Promise<void>
registerMessageListener(
cb: (data: { message: string }) => void
Expand Down
Loading