diff --git a/.gitignore b/.gitignore index cf966f8..29389c8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ .settings build/ + +node_modules/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..70e34ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "disabled" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index f245d03..27a504d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2019 lg2 +Copyright (c) 2025 Skura Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/RNEddystoneModule.podspec b/RNEddystoneModule.podspec new file mode 100644 index 0000000..348e518 --- /dev/null +++ b/RNEddystoneModule.podspec @@ -0,0 +1,45 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = "RNEddystoneModule" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.platforms = { :ios => "11.0" } + s.author = package["author"] + s.source = { :git => package["repository"], :tag => "#{s.version}" } + + + s.source_files = [ + "ios/**/*.{h,m,mm}", + ] + # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. + # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + + # Don't install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end + end + +end diff --git a/android/build.gradle b/android/build.gradle index a28f0dd..8c343d4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,36 +1,50 @@ - buildscript { + ext.safeExtGet = {prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } repositories { - jcenter() + google() + gradlePluginPortal() } - dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' + classpath("com.android.tools.build:gradle:7.0.4") } } - +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" + } apply plugin: 'com.android.library' +if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' +} + android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion safeExtGet('compileSdkVersion', 31) defaultConfig { - minSdkVersion 16 - targetSdkVersion 22 - versionCode 1 - versionName "1.0" - } - lintOptions { - abortOnError false + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) } } repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../node_modules/react-native/android" + } mavenCentral() + google() } dependencies { - compile 'com.facebook.react:react-native:+' + implementation 'com.facebook.react:react-native:+' } - \ No newline at end of file + + if (isNewArchitectureEnabled()) { + react { + jsRootDir = file("../src/") + libraryName = "RNEddystoneModule" + codegenJavaPackageName = "com.eddystone" + } + } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index c34c720..ffc794f 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,11 @@ + package="com.eddystone"> + + + + + \ No newline at end of file diff --git a/android/src/main/java/com/eddystone/EddystoneModule.java b/android/src/main/java/com/eddystone/EddystoneModule.java new file mode 100644 index 0000000..b296ac2 --- /dev/null +++ b/android/src/main/java/com/eddystone/EddystoneModule.java @@ -0,0 +1,347 @@ +package com.eddystone; + +import android.Manifest; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.ParcelUuid; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +class EddystoneModule extends NativeEddystoneManagerSpec { + + public static final String LOG_TAG = "RNEddystoneModule"; + /** @property {ReactApplicationContext} The react app context */ + private final ReactApplicationContext reactContext; + + /** @property {BluetoothAdapter} The Bluetooth Adapter instance */ + private BluetoothAdapter bluetoothAdapter; + + /** @property {BluetoothLeScanner} The Bluetooth LE scanner instance */ + private BluetoothLeScanner scanner; + + /** @property ParcelUuid The service id for Eddystone beacons */ + public static final ParcelUuid SERVICE_UUID = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"); + + /** @property ParcelUuid The configuration service id for Eddystone beacon */ + public static final ParcelUuid CONFIGURATION_UUID = ParcelUuid.fromString("a3c87500-8ed3-4bdf-8a39-a01bebede295"); + + /** @property byte UID frame type byte identifier */ + public static final byte FRAME_TYPE_UID = 0x00; + + /** @property byte URL frame type byte identifier */ + public static final byte FRAME_TYPE_URL = 0x10; + + /** @property byte TLM frame type byte identifier */ + public static final byte FRAME_TYPE_TLM = 0x20; + + /** @property byte EID frame type byte identifier */ + public static final byte FRAME_TYPE_EID = 0x30; + + /** @property byte Empty frame type byte identifier */ + public static final byte FRAME_TYPE_EMPTY = 0x40; + + public ReactApplicationContext getReactContext() { + return reactContext; + } + + public EddystoneModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + } + public static final String NAME = "EddystoneModule"; + @NonNull + @Override + public String getName() { + return NAME; + } + + private BluetoothAdapter getBluetoothAdapter() { + if (bluetoothAdapter == null) { + BluetoothManager manager = (BluetoothManager) reactContext.getSystemService(Context.BLUETOOTH_SERVICE); + bluetoothAdapter = manager.getAdapter(); + } + return bluetoothAdapter; + } + + /** + * Returns a URL scheme based on a URL Frame hexChar + * + * @param {byte} The hexChar to analyse for a scheme + * @returns {String} The URL scheme found + * @public + */ + private String getURLScheme(byte hexChar) { + switch (hexChar) { + case 0x00: + return "http://www."; + case 0x01: + return "https://www."; + case 0x02: + return "http://"; + case 0x03: + return "https://"; + default: + return null; + } + } + + /** + * Returns an encoded string or URL suffix based on a URL frame hexChar + * + * @param {byte} hexChar The hexChar to analyse for a scheme + * @returns {String} The encoded string or URL suffix found + * @public + */ + private String getEncodedString(byte hexChar) { + switch (hexChar) { + case 0x00: + return ".com/"; + case 0x01: + return ".org/"; + case 0x02: + return ".edu/"; + case 0x03: + return ".net/"; + case 0x04: + return ".info/"; + case 0x05: + return ".biz/"; + case 0x06: + return ".gov/"; + case 0x07: + return ".com"; + case 0x08: + return ".org"; + case 0x09: + return ".edu"; + case 0x0a: + return ".net"; + case 0x0b: + return ".info"; + case 0x0c: + return ".biz"; + case 0x0d: + return ".gov"; + default: + byte[] byteArray = new byte[] { hexChar }; + return new String(byteArray); + } + } + + /** @property {ScanCallback} Callbacks execute when a device is scanned */ + ScanCallback scanCallback = new ScanCallback() { + /** + * Triggered when a device is scanned + * + * @param {int} callbackType The type of callback triggered + * @param {ScanResult} result The device result object + * @returns {void} + * @public + */ + @Override + public void onScanResult(int callbackType, ScanResult result) { + handleResult(result); + } + + /** + * Triggered when many devices were scanned + * + * @param {List} results The devices results objects + * @returns {void} + * @public + */ + @Override + public void onBatchScanResults(List results) { + for (ScanResult result : results) { + handleResult(result); + } + } + + /** + * Handles a single device's results + * + * @param {ScanResult} result The device's result object + * @returns {void} + * @public + */ + public void handleResult(ScanResult result) { + // attempt to get sevice data from eddystone uuid + byte[] serviceData = result.getScanRecord().getServiceData(SERVICE_UUID); + + // fallback on configuration uuid if necessary + if (serviceData == null || serviceData.length == 0) { + serviceData = result.getScanRecord().getServiceData(CONFIGURATION_UUID); + + if (serviceData == null) { + return; + } + } + + // handle all possible frame types + byte frameType = serviceData[0]; + if (frameType == FRAME_TYPE_UID || frameType == FRAME_TYPE_EID) { + int length = 18; + String event = "onUIDFrame"; + + if (frameType == FRAME_TYPE_EID) { + length = 8; + event = "onEIDFrame"; + } + + // reconstruct the beacon id from hex array + StringBuilder builder = new StringBuilder(); + for (int i = 2; i < length; i++) { + builder.append(Integer.toHexString(serviceData[i] & 0xFF)); + } + + // create params object for javascript thread + WritableMap params = Arguments.createMap(); + params.putString("id", builder.toString()); + params.putString("uid", result.getDevice().getAddress()); + params.putInt("txPower", serviceData[1]); + params.putInt("rssi", result.getRssi()); + + // dispatch event + if (frameType == FRAME_TYPE_UID) + emitOnUIDFrame(params); + else if (frameType == FRAME_TYPE_EID) { + emitOnEIDFrame(params); + } + // emit(event, params); + } else if (frameType == FRAME_TYPE_URL) { + + // build the url from the frame's bytes + String url = getURLScheme(serviceData[2]); + for (int i = 3; i < 17 + 3; i++) { + if (serviceData.length <= i) { + break; + } + + url += getEncodedString(serviceData[i]); + } + WritableMap params = Arguments.createMap(); + params.putString("url", url); + // dispatch event + // emit("onURLFrame", url); + emitOnURLFrame(params); + } else if (frameType == FRAME_TYPE_TLM) { + // grab the beacon's voltage + int voltage = (serviceData[2] & 0xFF) << 8; + voltage += (serviceData[3] & 0xFF); + + // grab the beacon's temperature + int temp = (serviceData[4] << 8); + temp += (serviceData[5] & 0xFF); + temp /= 256f; + + // create params object for javascript thread + WritableMap params = Arguments.createMap(); + params.putInt("voltage", voltage); + params.putInt("temp", temp); + + // dispatch event + // emit("onTelemetryFrame", params); + emitOnTelemetryData(params); + } else if (frameType == FRAME_TYPE_EMPTY) { + // dispatch empty event + // emit("onEmptyFrame", null); + emitOnEmptyFrame("empty"); + } + } + }; + + /** + * Starts scanning for Eddystone beacons + * + * @returns {void} + * @public + */ + @ReactMethod + public void startScanning() { + ScanFilter serviceFilter = new ScanFilter.Builder().setServiceUuid(SERVICE_UUID).build(); + + ScanFilter configurationFilter = new ScanFilter.Builder().setServiceUuid(CONFIGURATION_UUID).build(); + + final List filters = new ArrayList<>(); + filters.add(serviceFilter); + filters.add(configurationFilter); + + ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); + + Objects.requireNonNull(reactContext.getCurrentActivity()).requestPermissions( + new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, + 1); + + reactContext.getCurrentActivity().requestPermissions( + new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, + 1); + + if (!bluetoothAdapter.isEnabled()) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + reactContext.getCurrentActivity().startActivityForResult(enableBtIntent, 8123); + } + + // start scanning + scanner = bluetoothAdapter.getBluetoothLeScanner(); + scanner = bluetoothAdapter.getBluetoothLeScanner(); + if (scanner != null) { + if (ActivityCompat.checkSelfPermission(this.getReactContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return; + } + scanner.startScan(filters, settings, scanCallback); + } + } + + /** + * Stops scanning for Eddystone beacons + * + * @returns {void} + * @public + */ + @ReactMethod + public void stopScanning() { + if (scanner != null) { + if (ActivityCompat.checkSelfPermission(this.getReactContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return; + } + scanner.stopScan(scanCallback); + } + scanner = null; + } + +} diff --git a/android/src/main/java/com/eddystone/EddystonePackage.java b/android/src/main/java/com/eddystone/EddystonePackage.java new file mode 100644 index 0000000..10a5e11 --- /dev/null +++ b/android/src/main/java/com/eddystone/EddystonePackage.java @@ -0,0 +1,47 @@ +/** + * React Native Eddystone + * + * A simple Eddystone implementation in React Native for both iOS and Android. + * + * + * @package original @lg2/react-native-eddystone + * @package react-native-eddystone + * @link https://github.com/lg2/react-native-eddystone + * @copyright 2025 Skura + * @license MIT + */ + +package com.eddystone; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.bridge.JavaScriptModule; + +public class EddystonePackage implements ReactPackage { + + public EddystonePackage(){ + } + @Override + public List createNativeModules(ReactApplicationContext reactApplicationContext) { + List modules = new ArrayList<>(); + + modules.add(new EddystoneModule(reactApplicationContext)); + return modules; + } + + public List> createJSModules() { + return new ArrayList<>(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactApplicationContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/lg2/eddystone/EddystoneModule.java b/android/src/main/java/com/lg2/eddystone/EddystoneModule.java deleted file mode 100644 index 86f8ea2..0000000 --- a/android/src/main/java/com/lg2/eddystone/EddystoneModule.java +++ /dev/null @@ -1,324 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -package com.lg2.eddystone; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; - -import android.Manifest; -import android.content.*; -import android.bluetooth.*; -import android.bluetooth.le.*; -import android.os.ParcelUuid; - -import java.util.List; -import java.util.ArrayList; - -public class EddystoneModule extends ReactContextBaseJavaModule { - /** @property {ReactApplicationContext} The react app context */ - private final ReactApplicationContext reactContext; - - /** @property {BluetoothAdapter} The Bluetooth Adapter instance */ - private BluetoothAdapter bluetoothAdapter; - - /** @property {BluetoothLeScanner} The Bluetooth LE scanner instance */ - private BluetoothLeScanner scanner; - - /** @property ParcelUuid The service id for Eddystone beacons */ - public static final ParcelUuid SERVICE_UUID = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"); - - /** @property ParcelUuid The configuration service id for Eddystone beacon */ - public static final ParcelUuid CONFIGURATION_UUID = ParcelUuid.fromString("a3c87500-8ed3-4bdf-8a39-a01bebede295"); - - /** @property byte UID frame type byte identifier */ - public static final byte FRAME_TYPE_UID = 0x00; - - /** @property byte URL frame type byte identifier */ - public static final byte FRAME_TYPE_URL = 0x10; - - /** @property byte TLM frame type byte identifier */ - public static final byte FRAME_TYPE_TLM = 0x20; - - /** @property byte EID frame type byte identifier */ - public static final byte FRAME_TYPE_EID = 0x30; - - /** @property byte Empty frame type byte identifier */ - public static final byte FRAME_TYPE_EMPTY = 0x40; - - /** - * EddystoneModule class constructor - * - * @param {ReactApplicationContext} reactContext React native application - * context - * @constructor - */ - public EddystoneModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - } - - /** - * Returns the name of the module - * - * @returns {String} The name of the module - * @public - */ - @Override - public String getName() { - return "Eddystone"; - } - - /** - * Dispatches an event to the javascript thread - * - * @param {String} event The name of the dispatched event - * @param {Object} params The parameters related to that event - * @returns {void} - * @public - */ - private void emit(String event, Object params) { - reactContext.getJSModule(RCTDeviceEventEmitter.class).emit(event, params); - } - - /** - * Returns a URL scheme based on a URL Frame hexChar - * - * @param {byte} The hexChar to analyse for a scheme - * @returns {String} The URL scheme found - * @public - */ - private String getURLScheme(byte hexChar) { - switch (hexChar) { - case 0x00: - return "http://www."; - case 0x01: - return "https://www."; - case 0x02: - return "http://"; - case 0x03: - return "https://"; - default: - return null; - } - } - - /** - * Returns an encoded string or URL suffix based on a URL frame hexChar - * - * @param {byte} hexChar The hexChar to analyse for a scheme - * @returns {String} The encoded string or URL suffix found - * @public - */ - private String getEncodedString(byte hexChar) { - switch (hexChar) { - case 0x00: - return ".com/"; - case 0x01: - return ".org/"; - case 0x02: - return ".edu/"; - case 0x03: - return ".net/"; - case 0x04: - return ".info/"; - case 0x05: - return ".biz/"; - case 0x06: - return ".gov/"; - case 0x07: - return ".com"; - case 0x08: - return ".org"; - case 0x09: - return ".edu"; - case 0x0a: - return ".net"; - case 0x0b: - return ".info"; - case 0x0c: - return ".biz"; - case 0x0d: - return ".gov"; - default: - byte[] byteArray = new byte[] { hexChar }; - return new String(byteArray); - } - } - - /** @property {ScanCallback} Callbacks execute when a device is scanned */ - ScanCallback scanCallback = new ScanCallback() { - /** - * Triggered when a device is scanned - * - * @param {int} callbackType The type of callback triggered - * @param {ScanResult} result The device result object - * @returns {void} - * @public - */ - @Override - public void onScanResult(int callbackType, ScanResult result) { - handleResult(result); - } - - /** - * Triggered when many devices were scanned - * - * @param {List} results The devices results objects - * @returns {void} - * @public - */ - @Override - public void onBatchScanResults(List results) { - for (ScanResult result : results) { - handleResult(result); - } - } - - /** - * Handles a single device's results - * - * @param {ScanResult} result The device's result object - * @returns {void} - * @public - */ - public void handleResult(ScanResult result) { - // attempt to get sevice data from eddystone uuid - byte[] serviceData = result.getScanRecord().getServiceData(SERVICE_UUID); - - // fallback on configuration uuid if necessary - if (serviceData == null || serviceData.length == 0) { - serviceData = result.getScanRecord().getServiceData(CONFIGURATION_UUID); - - if (serviceData == null) { - return; - } - } - - // handle all possible frame types - byte frameType = serviceData[0]; - if (frameType == FRAME_TYPE_UID || frameType == FRAME_TYPE_EID) { - int length = 16; - String event = "onUIDFrame"; - - if (frameType == FRAME_TYPE_EID) { - length = 8; - event = "onEIDFrame"; - } - - // reconstruct the beacon id from hex array - StringBuilder builder = new StringBuilder(); - for (int i = 2; i < length; i++) { - builder.append(Integer.toHexString(serviceData[i] & 0xFF)); - } - - // create params object for javascript thread - WritableMap params = Arguments.createMap(); - params.putString("id", builder.toString()); - params.putString("uid", result.getDevice().getAddress()); - params.putInt("txPower", serviceData[1]); - params.putInt("rssi", result.getRssi()); - - // dispatch event - emit(event, params); - } else if (frameType == FRAME_TYPE_URL) { - - // build the url from the frame's bytes - String url = getURLScheme(serviceData[2]); - for (int i = 3; i < 17 + 3; i++) { - if (serviceData.length <= i) { - break; - } - - url += getEncodedString(serviceData[i]); - } - - // dispatch event - emit("onURLFrame", url); - } else if (frameType == FRAME_TYPE_TLM) { - // grab the beacon's voltage - int voltage = (serviceData[2] & 0xFF) << 8; - voltage += (serviceData[3] & 0xFF); - - // grab the beacon's temperature - int temp = (serviceData[4] << 8); - temp += (serviceData[5] & 0xFF); - temp /= 256f; - - // create params object for javascript thread - WritableMap params = Arguments.createMap(); - params.putInt("voltage", voltage); - params.putInt("temp", temp); - - // dispatch event - emit("onTelemetryFrame", params); - } else if (frameType == FRAME_TYPE_EMPTY) { - // dispatch empty event - emit("onEmptyFrame", null); - } - } - }; - - /** - * Starts scanning for Eddystone beacons - * - * @returns {void} - * @public - */ - @ReactMethod - public void startScanning() { - ScanFilter serviceFilter = new ScanFilter.Builder().setServiceUuid(SERVICE_UUID).build(); - - ScanFilter configurationFilter = new ScanFilter.Builder().setServiceUuid(CONFIGURATION_UUID).build(); - - final List filters = new ArrayList<>(); - filters.add(serviceFilter); - filters.add(configurationFilter); - - ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); - - getCurrentActivity().requestPermissions( - new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, - 1 - ); - - getCurrentActivity().requestPermissions( - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - 1 - ); - - if (!bluetoothAdapter.isEnabled()) { - Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - getCurrentActivity().startActivityForResult(enableBtIntent, 8123); - } - - // start scanning - scanner = bluetoothAdapter.getBluetoothLeScanner(); - scanner.startScan(filters, settings, scanCallback); - } - - /** - * Stops scanning for Eddystone beacons - * - * @returns {void} - * @public - */ - @ReactMethod - public void stopScanning() { - scanner.stopScan(scanCallback); - scanner = null; - } -} diff --git a/android/src/main/java/com/lg2/eddystone/EddystonePackage.java b/android/src/main/java/com/lg2/eddystone/EddystonePackage.java deleted file mode 100644 index f47d288..0000000 --- a/android/src/main/java/com/lg2/eddystone/EddystonePackage.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -package com.lg2.eddystone; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; - -public class EddystonePackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new EddystoneModule(reactContext)); - } - - // Deprecated from RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} \ No newline at end of file diff --git a/examples/basic.js b/examples/basic.js index 1770b71..8bb8b73 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -1,7 +1,7 @@ import React, { Component } from "react"; import { View } from "react-native"; import Eddystone from "@lg2/react-native-eddystone"; - +//THIS SHOULD BE UPDATED:.. export default class App extends Component { componentDidMount() { // bind eddystone events diff --git a/ios/Beacon.h b/ios/Beacon.h index bf432de..9fcb0e6 100644 --- a/ios/Beacon.h +++ b/ios/Beacon.h @@ -2,10 +2,10 @@ * React Native Eddystone * * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone + * @package original @lg2/react-native-eddystone + * @package react-native-eddystone * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 + * @copyright 2025 Skura * @license MIT */ diff --git a/ios/Beacon.m b/ios/Beacon.m index 9dc9b22..a4c43e5 100644 --- a/ios/Beacon.m +++ b/ios/Beacon.m @@ -3,9 +3,10 @@ * * A simple Eddystone implementation in React Native for both iOS and Android. * - * @package @lg2/react-native-eddystone + * @package original @lg2/react-native-eddystone + * @package react-native-eddystone * * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 + * @copyright 2020 SKura * @license MIT */ diff --git a/ios/Eddystone.h b/ios/Eddystone.h deleted file mode 100644 index 5a1cb6b..0000000 --- a/ios/Eddystone.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -#import -#import - -@interface Eddystone : RCTEventEmitter -/** - * Eddystone class initializer - * @return instancetype - */ -- (instancetype)init; -@end diff --git a/ios/Eddystone.m b/ios/Eddystone.m deleted file mode 100644 index f036d79..0000000 --- a/ios/Eddystone.m +++ /dev/null @@ -1,201 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -#import -#import "Eddystone.h" -#import "Beacon.h" - -// declare our module interface & implement it as a central manager delegate -@interface Eddystone () { -@private - /** @property BOOL Whether we should be scanning for devices or not */ - BOOL _shouldBeScanning; - - // core bluetooth central manager - CBCentralManager *_centralManager; - - // our beacon dispatch queue - dispatch_queue_t _beaconOperationsQueue; -} -@end - -@implementation Eddystone - // react-native module macro - RCT_EXPORT_MODULE() - - // react native methods - + (BOOL)requiresMainQueueSetup { return NO; } - - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } - - /** - * Eddystone class initializer - * @return instancetype - */ - - (instancetype)init { - if ((self = [super init]) != nil) { - _beaconOperationsQueue = dispatch_queue_create("EddystoneBeaconOperationsQueue", NULL); - _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:_beaconOperationsQueue]; - } - - return self; - } - - /** - * Lists the supported events for the RCTEventEmitter - * @return NSArray * The supported events list - */ - - (NSArray *)supportedEvents { - return @[ - @"onUIDFrame", - @"onEIDFrame", - @"onURLFrame", - @"onTelemetryFrame", - @"onEmptyFrame", - @"onStateChanged" - ]; - } - - /** - * Exported method that starts scanning for eddystone devices - * @return void - */ - RCT_EXPORT_METHOD(startScanning) { - dispatch_async(_beaconOperationsQueue, ^{ - if (_centralManager.state != CBCentralManagerStatePoweredOn) { - _shouldBeScanning = YES; - } else { - NSArray *services = @[[CBUUID UUIDWithString:SERVICE_ID]]; - NSDictionary *options = @{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }; - [_centralManager scanForPeripheralsWithServices:services options:options]; - } - }); - } - - /** - * Exported method that stops scanning for eddystone devices - * @return void - */ - RCT_EXPORT_METHOD(stopScanning) { - _shouldBeScanning = NO; - [_centralManager stopScan]; - } - - /** - * Executes when the Core Bluetooth Central Manager discovered a peripheral - * @param CBCentralManager * central Core Bluetooth Central Manager instance - * @param CBPeripheral * peripheral Core Bluetooth peripheral instance - * @param NSDictionary * advertisementData Peripheral advertised data - * @param NSNumber * RSSI The received signal strength indication - * @return void - */ - - (void)centralManager:(CBCentralManager *)central - didDiscoverPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI { - // retrieve the beacon data from the advertised data - NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey]; - - // retrieve the frame type - FrameType frameType = [Beacon getFrameType:serviceData]; - - // handle basic beacon broadcasts - if (frameType == FrameTypeUID || frameType == FrameTypeEID) { - // create our beacon object based on the frame type - Beacon *beacon; - NSString *eventName; - if (frameType == FrameTypeUID) { - eventName = @"onUIDFrame"; - beacon = [Beacon initWithUIDFrameType:serviceData rssi:RSSI]; - } else if(frameType == FrameTypeEID) { - eventName = @"onEIDFrame"; - beacon = [Beacon initWithEIDFrameType:serviceData rssi:RSSI]; - } - - // dispatch device event with beacon information - [self sendEventWithName:eventName - body:@{ - @"id": [NSString stringWithFormat:@"%@", beacon.id], - @"uid": [peripheral.identifier UUIDString], - @"txPower": beacon.txPower, - @"rssi": beacon.rssi - }]; - } else if(frameType == FrameTypeURL) { - // retrive the URL from the beacon broadcast & dispatch - NSURL *url = [Beacon getUrl:serviceData]; - [self sendEventWithName:@"onURLFrame" body:@{ - @"uid": [peripheral.identifier UUIDString], - @"url": url.absoluteString - }]; - } else if (frameType == FrameTypeTelemetry) { - // retrieve the beacon data - NSData *beaconData = [Beacon getData:serviceData]; - uint8_t *bytes = (uint8_t *)[beaconData bytes]; - - // attempt to match a frame type - if (beaconData) { - if ([beaconData length] > 1) { - int voltage = (bytes[2] & 0xFF) << 8; - voltage += (bytes[3] & 0xFF); - - int temp = (bytes[4] << 8); - temp += (bytes[5] & 0xFF); - temp /= 256.f; - - // dispatch telemetry information - [self sendEventWithName:@"onTelemetryFrame" body:@{ - @"uid": [peripheral.identifier UUIDString], - @"voltage": [NSNumber numberWithInt: voltage], - @"temp": [NSNumber numberWithInt: temp] - }]; - } - } - - } else if (frameType == FrameTypeEmpty){ - // dispatch empty frame - [self sendEventWithName:@"onEmptyFrame" body:nil]; - } - } - - /** - * Executes when the Core Bluetooth Central Manager's state changes - * @param CBCentralManager manager The Central Manager instance - * @return void - */ - - (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)manager { - switch(manager.state) { - case CBCentralManagerStatePoweredOn: - [self sendEventWithName:@"onStateChanged" body:@"on"]; - if(_shouldBeScanning) { - [self startScanning]; - } - break; - - case CBCentralManagerStatePoweredOff: - [self sendEventWithName:@"onStateChanged" body:@"off"]; - break; - - case CBCentralManagerStateResetting: - [self sendEventWithName:@"onStateChanged" body:@"resetting"]; - break; - - case CBCentralManagerStateUnsupported: - [self sendEventWithName:@"onStateChanged" body:@"unsupported"]; - break; - - case CBCentralManagerStateUnauthorized: - [self sendEventWithName:@"onStateChanged" body:@"unauthorized"]; - break; - - default: - [self sendEventWithName:@"onStateChanged" body:@"unknown"]; - } - } -@end diff --git a/ios/Eddystone.podspec b/ios/Eddystone.podspec deleted file mode 100644 index 9a9fe2b..0000000 --- a/ios/Eddystone.podspec +++ /dev/null @@ -1,24 +0,0 @@ - -Pod::Spec.new do |s| - s.name = "Eddystone" - s.version = "1.0.0" - s.summary = "Eddystone" - s.description = <<-DESC - Eddystone - DESC - s.homepage = "https://github.com/lg2/react-native-eddystone" - s.license = "MIT" - # s.license = { :type => "MIT", :file => "FILE_LICENSE" } - s.author = { "author" => "author@domain.cn" } - s.platform = :ios, "7.0" - s.source = { :git => "https://github.com/lg2/react-native-eddystone", :tag => "master" } - s.source_files = "**/*.{h,m}" - s.requires_arc = true - - - s.dependency "React" - #s.dependency "others" - -end - - diff --git a/ios/Eddystone.xcodeproj/project.pbxproj b/ios/Eddystone.xcodeproj/project.pbxproj index 0008a41..eb8d97f 100644 --- a/ios/Eddystone.xcodeproj/project.pbxproj +++ b/ios/Eddystone.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2E8A60DD2D4C2E8F0030404B /* EddystoneModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2E8A60DB2D4C2E8F0030404B /* EddystoneModule.mm */; }; 2E9B3B3E21ED254800D52F4E /* Beacon.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E9B3B3B21ED254800D52F4E /* Beacon.m */; }; B3E7B58A1CC2AC0600A0062D /* Eddystone.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* Eddystone.m */; }; /* End PBXBuildFile section */ @@ -25,6 +26,9 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libEddystone.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEddystone.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E8A60DA2D4C2E8F0030404B /* EddystoneModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EddystoneModule.h; sourceTree = ""; }; + 2E8A60DB2D4C2E8F0030404B /* EddystoneModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EddystoneModule.mm; sourceTree = ""; }; + 2E8A60DC2D4C2E8F0030404B /* EddystoneModule-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EddystoneModule-Bridging-Header.h"; sourceTree = ""; }; 2E9B3B3B21ED254800D52F4E /* Beacon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Beacon.m; sourceTree = ""; }; 2E9B3B3D21ED254800D52F4E /* Beacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Beacon.h; sourceTree = ""; }; B3E7B5881CC2AC0600A0062D /* Eddystone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Eddystone.h; sourceTree = ""; }; @@ -53,6 +57,9 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 2E8A60DA2D4C2E8F0030404B /* EddystoneModule.h */, + 2E8A60DB2D4C2E8F0030404B /* EddystoneModule.mm */, + 2E8A60DC2D4C2E8F0030404B /* EddystoneModule-Bridging-Header.h */, 2E9B3B3D21ED254800D52F4E /* Beacon.h */, 2E9B3B3B21ED254800D52F4E /* Beacon.m */, B3E7B5881CC2AC0600A0062D /* Eddystone.h */, @@ -100,6 +107,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 58B511D21A9E6C8500147676; @@ -119,6 +127,7 @@ files = ( B3E7B58A1CC2AC0600A0062D /* Eddystone.m in Sources */, 2E9B3B3E21ED254800D52F4E /* Beacon.m in Sources */, + 2E8A60DD2D4C2E8F0030404B /* EddystoneModule.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Eddystone.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Eddystone.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Eddystone.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Eddystone.xcodeproj/project.xcworkspace/xcuserdata/jaio.xcuserdatad/UserInterfaceState.xcuserstate b/ios/Eddystone.xcodeproj/project.xcworkspace/xcuserdata/jaio.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..a42670b Binary files /dev/null and b/ios/Eddystone.xcodeproj/project.xcworkspace/xcuserdata/jaio.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/Eddystone.xcodeproj/xcuserdata/jaio.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/Eddystone.xcodeproj/xcuserdata/jaio.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..67e4fb7 --- /dev/null +++ b/ios/Eddystone.xcodeproj/xcuserdata/jaio.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Eddystone.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/ios/EddystoneModule-Bridging-Header.h b/ios/EddystoneModule-Bridging-Header.h new file mode 100644 index 0000000..c397775 --- /dev/null +++ b/ios/EddystoneModule-Bridging-Header.h @@ -0,0 +1,6 @@ +#import +#import + +#ifdef RCT_NEW_ARCH_ENABLED +#endif + diff --git a/ios/EddystoneModule.h b/ios/EddystoneModule.h new file mode 100644 index 0000000..6d83888 --- /dev/null +++ b/ios/EddystoneModule.h @@ -0,0 +1,37 @@ +#import +#import + +#ifdef RCT_NEW_ARCH_ENABLED + +#import +@class Eddystone; + +@interface EddystoneModule : NativeEddystoneModuleSpecBase +- (void)emitOnUIDFrame:(NSDictionary *)value; +- (void)emitOnURLFrame:(NSDictionary *)value; +- (void)emitOnEIDFrame:(NSDictionary *)value; +- (void)emitOnTelemetryData:(NSDictionary *)value; +- (void)emitOnEmptyFrame:(NSDictionary *)value; +- (void)emitOnStateChanged:(NSDictionary *)value; +//+ (nullable CBCentralManager *)getCentralManager; +//+ (nullable SwiftBleManager *)getInstance; +@end + +#else + +@interface EddystoneModule : NSObject +- (void)emitOnUIDFrame:(NSDictionary *)value; +- (void)emitOnURLFrame:(NSDictionary *)value; +- (void)emitOnEIDFrame:(NSDictionary *)value; +- (void)emitOnTelemetryData:(NSDictionary *)value; +- (void)emitOnEmptyFrame:(NSDictionary *)value; +- (void)emitOnStateChanged:(NSDictionary *)value; +//+ (nullable CBCentralManager *)getCentralManager; +//+ (nullable SwiftBleManager *)getInstance; +@end + +#endif + +@interface SpecChecker : NSObject ++ (BOOL)isSpecAvailable; +@end diff --git a/ios/EddystoneModule.mm b/ios/EddystoneModule.mm new file mode 100644 index 0000000..60f0d12 --- /dev/null +++ b/ios/EddystoneModule.mm @@ -0,0 +1,200 @@ +#import "EddystoneModule.h" +#import +#import "Beacon.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import "EddystoneModuleSpec.h" +#endif + + +@interface EddystoneModule () { +@private + /** @property BOOL Whether we should be scanning for devices or not */ + BOOL _shouldBeScanning; + + // core bluetooth central manager + CBCentralManager *_centralManager; + + // our beacon dispatch queue + dispatch_queue_t _beaconOperationsQueue; +} +@end + +@implementation EddystoneModule + +- (instancetype)init { + if ((self = [super init]) != nil) { + _beaconOperationsQueue = dispatch_queue_create("EddystoneBeaconOperationsQueue", NULL); + _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:_beaconOperationsQueue]; + } + return self; +} + + +RCT_EXPORT_MODULE() + +- (void)startScanning { + dispatch_async(_beaconOperationsQueue, ^{ + if (self->_centralManager.state != CBCentralManagerStatePoweredOn) { + self->_shouldBeScanning = YES; + } else { + NSArray *services = @[[CBUUID UUIDWithString:SERVICE_ID]]; + NSDictionary *options = @{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }; + [self->_centralManager scanForPeripheralsWithServices:services options:options]; + } + }); +} + + +- (void)stopScannig { + _shouldBeScanning = NO; + [_centralManager stopScan]; +} + + +//- (void)setName:(NSString *)name { +// [_eddystone setName:name]; +//} + +/** + * Executes when the Core Bluetooth Central Manager discovered a peripheral + * @param CBCentralManager * central Core Bluetooth Central Manager instance + * @param CBPeripheral * peripheral Core Bluetooth peripheral instance + * @param NSDictionary * advertisementData Peripheral advertised data + * @param NSNumber * RSSI The received signal strength indication + * @return void + */ +- (void)centralManager:(CBCentralManager *)central + didDiscoverPeripheral:(CBPeripheral *)peripheral + advertisementData:(NSDictionary *)advertisementData + RSSI:(NSNumber *)RSSI { + if(hasListeners){ + // retrieve the beacon data from the advertised data + NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey]; + + // retrieve the frame type + FrameType frameType = [Beacon getFrameType:serviceData]; + + // handle basic beacon broadcasts + if (frameType == FrameTypeUID || frameType == FrameTypeEID) { + // create our beacon object based on the frame type + Beacon *beacon; + NSString *eventName; + if (frameType == FrameTypeUID) { + eventName = @"onUIDFrame"; + beacon = [Beacon initWithUIDFrameType:serviceData rssi:RSSI]; + } else if(frameType == FrameTypeEID) { + eventName = @"onEIDFrame"; + beacon = [Beacon initWithEIDFrameType:serviceData rssi:RSSI]; + } + //dispatch device event with beacon information + [_eddystoneModule emitOnUIDFrame:@{ + @"id": [NSString stringWithFormat:@"%@", beacon.id], + @"uid": [peripheral.identifier UUIDString], + @"txPower": beacon.txPower, + @"rssi": beacon.rssi + }]; + // dispatch device event with beacon information + // [self sendEventWithName:eventName + // body:@{ + // @"id": [NSString stringWithFormat:@"%@", beacon.id], + // @"uid": [peripheral.identifier UUIDString], + // @"txPower": beacon.txPower, + // @"rssi": beacon.rssi + // }]; + } else if(frameType == FrameTypeURL) { + // retrive the URL from the beacon broadcast & dispatch + NSURL *url = [Beacon getUrl:serviceData]; + [self emitOnURLFrame:@{ + @"uid": [peripheral.identifier UUIDString], + @"url": url.absoluteString + }]; + // [self sendEventWithName:@"onURLFrame" body:@{ + // @"uid": [peripheral.identifier UUIDString], + // @"url": url.absoluteString + // }]; + } else if (frameType == FrameTypeTelemetry) { + // retrieve the beacon data + NSData *beaconData = [Beacon getData:serviceData]; + uint8_t *bytes = (uint8_t *)[beaconData bytes]; + + // attempt to match a frame type + if (beaconData) { + if ([beaconData length] > 1) { + int voltage = (bytes[2] & 0xFF) << 8; + voltage += (bytes[3] & 0xFF); + + int temp = (bytes[4] << 8); + temp += (bytes[5] & 0xFF); + temp /= 256.f; + [self emitOnTelemetryData:@{ + @"uid": [peripheral.identifier UUIDString], + @"voltage": [NSNumber numberWithInt: voltage], + @"temp": [NSNumber numberWithInt: temp] + }]; + // // dispatch telemetry information + // [self sendEventWithName:@"onTelemetryFrame" body:@{ + // @"uid": [peripheral.identifier UUIDString], + // @"voltage": [NSNumber numberWithInt: voltage], + // @"temp": [NSNumber numberWithInt: temp] + // }]; + } + } + + } else if (frameType == FrameTypeEmpty){ + [self emitOnEmptyFrame:@{}]; + // dispatch empty frame + // [self sendEventWithName:@"onEmptyFrame" body:nil]; + } + } +} + +/** + * Executes when the Core Bluetooth Central Manager's state changes + * @param CBCentralManager manager The Central Manager instance + * @return void + */ +- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)manager { + switch(manager.state) { + case CBManagerStatePoweredOn: + // [self sendEventWithName:@"onStateChanged" body:@"on"]; + [self emitOnStateChanged:@{@"value":@"on"}]; + if(_shouldBeScanning) { + [self startScanning]; + } + break; + + case CBManagerStatePoweredOff: + [self emitOnStateChanged:@{@"value":@"off"}]; + break; + + case CBManagerStateResetting: + [self emitOnStateChanged:@{@"value":@"resseting"}]; + break; + + case CBManagerStateUnsupported: + //[self sendEventWithName:@"onStateChanged" body:@"unsupported"]; + [self emitOnStateChanged:@{@"value":@"unsupported"}]; + + break; + + case CBManagerStateUnauthorized: + //[self sendEventWithName:@"onStateChanged" body:@"unauthorized"]; + [self emitOnStateChanged:@{@"value":@"unauthorized"}]; + + break; + + default: + // [self sendEventWithName:@"onStateChanged" body:@"unknown"]; + [self emitOnStateChanged:@{@"value":@"unknown"}]; + + } +} +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: +(const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} +#endif +@end diff --git a/package.json b/package.json index 1632971..71005e9 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,40 @@ { - "name": "@lg2/react-native-eddystone", - "version": "0.1.5", - "description": "A simple Eddystone implementation in React Native for both iOS and Android.", - "main": "src/index.js", - "directories": { - "example": "examples" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/lg2/react-native-eddystone.git" - }, - "keywords": ["react", "native", "eddystone"], - "author": "Nicholas Charbonneau ", + "name": "react-native-eddystone", + "version": "2.0", + "description": "Showcase Turbomodule with backward compatibility", + "react-native": "src/index", + "source": "src/index", + "files": [ + "src", + "android", + "ios", + "*.podspec", + "!android/build", + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "react-native", + "ios", + "android" + ], + "repository": "https://github.com/JaioSkura/react-native-eddystone", + "author": " (https://github.com/)", "license": "MIT", "bugs": { - "url": "https://github.com/lg2/react-native-eddystone/issues" + "url": "https://github.com/JaioSkura/react-native-eddystone/issues" + }, + "homepage": "https://github.com/JaioSkura/react-native-eddystone#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" }, - "homepage": "https://github.com/lg2/react-native-eddystone#readme" + "codegenConfig": { + "name": "EddystoneModuleSpec", + "type": "modules", + "jsSrcsDir": "src" + } } diff --git a/src/Beacon.js b/src/Beacon.js deleted file mode 100644 index 8a04278..0000000 --- a/src/Beacon.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -class Beacon { - /** - * Beacon class constructor - * @param {BeaconData} data The beacon data to use for creation - * @param {Manager} manager A reference to the beacon's manager - */ - constructor(data, manager) { - this.id = data.id; - this.uid = data.uid; - this.rssi = data.rssi; - this.txPower = data.txPower; - - this.url = null; - this.temp = null; - this.voltage = null; - - this.manager = manager; - } - - /** - * Sets/resets the beacon' expiration timer - * @param {number} time The expiration value in milliseconds - * @returns {void} - */ - setExpiration(time) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.manager.onBeaconExpires(this), time); - } - - /** - * Gets the beacon' approximative distance from the device - * @returns {number} The aprroximative distance in meters - */ - getDistance() { - if (this.rssi == 0) return -1; - - const ratio = this.rssi / this.txPower; - - return ( - (ratio < 1.0 - ? Math.pow(ratio, 10) - : 0.89976 * Math.pow(ratio, 7.7095) + 0.111) / 1000 - ); - } -} - -export default Beacon; diff --git a/src/Manager.js b/src/Manager.js deleted file mode 100644 index 54e963f..0000000 --- a/src/Manager.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -import Beacon from "./Beacon"; -import EventEmitter from "eventemitter3"; -import { startScanning, stopScanning } from "./NativeModule"; -import { addListener, removeListener } from "./NativeEventEmitter"; - -class Manager extends EventEmitter { - /** - * Manager class constructor - * @param {number} expiration Beacon expiration time in milliseconds - */ - constructor(expiration) { - super(); - - this.beacons = []; - this.expiration = expiration || 10000; - this.events = { - addBeacon: this.addBeacon.bind(this), - addUrl: data => this._onData(data, this.addUrl.bind(this)), - addTelemetry: data => this._onData(data, this.addTelemetry.bind(this)) - }; - } - - /** - * Starts scanning for beacons - * @returns {void} - */ - start() { - addListener("onUIDFrame", this.events.addBeacon); - addListener("onEIDFrame", this.events.addBeacon); - addListener("onURLFrame", this.events.addUrl); - addListener("onTelemetryFrame", this.events.addTelemetry); - - startScanning(); - } - - /** - * Stops scanning for beacons - * @returns {void} - */ - stop() { - removeListener("onUIDFrame", this.events.addBeacon); - removeListener("onEIDFrame", this.events.addBeacon); - removeListener("onURLFrame", this.events.addUrl); - removeListener("onTelemetryFrame", this.events.addTelemetry); - - stopScanning(); - } - - /** - * Checks whether or not the manager has cached the beacon uid or not - * @param {string} uid The beacon uid to look for - * @returns {boolean} - */ - has(uid) { - return this.beacons.filter(beacon => uid === beacon.uid).length > 0; - } - - /** - * Adds a beacon to the manager's cache if it does not exist - * @param {BeaconData} data The beacons UID/EID information - * @returns {void} - */ - addBeacon(data) { - if (!this.has(data.uid)) { - const beacon = new Beacon(data, this); - beacon.setExpiration(this.expiration); - - this.beacons.push(beacon); - - this.emit("onBeaconAdded", beacon); - } - } - - /** - * Adds a URL to an existing beacon in the cache - * @param {Beacon} beacon The beacon to add url to - * @param {URLData} data The data containing the broadcasted URL - * @returns {void} - */ - addUrl(beacon, data) { - beacon.url = data.url; - beacon.setExpiration(this.expiration); - - this.emit("onBeaconUpdated", beacon); - } - - /** - * Adds telemetry info to an existing beacon in the cache - * @param {Beacon} beacon The beacon to add telemetry to - * @param {TelemetryData} data The data containing the broadcasted telemetry - * @returns {void} - */ - addTelemetry(beacon, data) { - beacon.temp = data.temp; - beacon.voltage = data.voltage; - beacon.setExpiration(this.expiration); - - this.emit("onBeaconUpdated", beacon); - } - - /** - * Triggered when a beacon has reached the end of its life - * @param {Beacon} beacon The expired beacon - * @returns {void} - */ - onBeaconExpires(beacon) { - if (this.has(beacon.uid)) { - this.beacons.splice( - this.beacons.findIndex(({ uid }) => beacon.uid === uid), - 1 - ); - - this.emit("onBeaconExpired", beacon); - } - } - - /** - * Triggered when a beacon message has been received by the bluetooth manager - * @param {BeaconData|URLData|TelemetryData} data The data received from the beacon - * @param {Function} callback The callback that will handle this data - * @returns {void} - */ - _onData(data, callback) { - if (this.has(data.uid)) { - const index = this.beacons.findIndex(beacon => beacon.uid === data.uid); - callback(this.beacons[index], data); - } - } -} - -export default Manager; diff --git a/src/NativeEddystoneManager.ts b/src/NativeEddystoneManager.ts new file mode 100644 index 0000000..26eba3c --- /dev/null +++ b/src/NativeEddystoneManager.ts @@ -0,0 +1,53 @@ +import { TurboModule, TurboModuleRegistry } from 'react-native'; +// @ts-ignore Ignore since it comes from codegen types. +import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes'; + +/** + * This represents the Turbo Module version of react-native-ble-manager. + * This adds the codegen definition to react-native generate the c++ bindings on compile time. + * That should work only on 0.75 and higher. + * Don't remove it! and please modify with caution! Knowing that can create wrong bindings into jsi and break at compile or execution time. + * - Knowing that also every type needs to match the current Objective C++ and Java callbacks types and callbacks type definitions and be aware of the current differences between implementation in both platforms. + */ + +export interface Spec extends TurboModule { + + startScanning(): void; + + stopScanning(): void; + + /** + * Supported events. + */ + + readonly onUIDFrame: EventEmitter; + readonly onEIDFrame: EventEmitter; + readonly onURLFrame: EventEmitter; + readonly onTelemetryData: EventEmitter; + readonly onEmptyFrame: EventEmitter; + readonly onStateChanged: EventEmitter; + + +} + +export default TurboModuleRegistry.get('EddystoneModule') as Spec; + +export type BeaconData ={ + id: string; + uid: string; + rssi: number; + txPower: number; +} + + +export type TelemetryData ={ + uid: string; + voltaje: number; + temp: number; +} + + +export type URLData ={ + uid: string; + url: string; +} \ No newline at end of file diff --git a/src/NativeEventEmitter.js b/src/NativeEventEmitter.js deleted file mode 100644 index 39b3f13..0000000 --- a/src/NativeEventEmitter.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -import { NativeModules, NativeEventEmitter } from "react-native"; - -const { Eddystone } = NativeModules; -const EddystoneEventEmitter = new NativeEventEmitter(Eddystone); - -const addListener = EddystoneEventEmitter.addListener.bind( - EddystoneEventEmitter -); - -const removeListener = EddystoneEventEmitter.removeListener.bind( - EddystoneEventEmitter -); - -export { addListener, removeListener }; diff --git a/src/NativeModule.js b/src/NativeModule.js deleted file mode 100644 index 9be9684..0000000 --- a/src/NativeModule.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -import { NativeModules } from "react-native"; - -const { Eddystone } = NativeModules; -const { startScanning, stopScanning } = Eddystone; - -export { startScanning, stopScanning }; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index b9ed999..0000000 --- a/src/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * React Native Eddystone - * - * A simple Eddystone implementation in React Native for both iOS and Android. - * - * @package @lg2/react-native-eddystone - * @link https://github.com/lg2/react-native-eddystone - * @copyright 2019 lg2 - * @license MIT - */ - -import Manager from "./Manager"; -import { startScanning, stopScanning } from "./NativeModule.js"; -import { addListener, removeListener } from "./NativeEventEmitter"; - -export default { - startScanning, - stopScanning, - addListener, - removeListener, - Manager -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..11eea80 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,58 @@ +import { + EventSubscription, + NativeModules, +} from 'react-native'; + +import { + URLData, + BeaconData, + TelemetryData, +} from './types'; +export * from './types'; + + +// @ts-expect-error This applies the turbo module version only when turbo is enabled for backwards compatibility. +const isTurboModuleEnabled = global?.__turboModuleProxy != null; + +const EddystoneModule = isTurboModuleEnabled + ? require('./NativeEddystoneManager').default + : NativeModules.EddystoneModule; + +class EddystoneManager { + constructor() { + if (!EddystoneModule) { + throw new Error('EddystoneManagerModule not found'); + } + } + + startScanning() { + EddystoneModule.startScanning(); + }; + + stopScanning() { + EddystoneModule.stopScanning(); + }; + + + onUIDFrame(callback: any): EventSubscription { + return EddystoneModule.onUIDFrame(callback); + } + + onEIDFrame(callback: any): EventSubscription { + return EddystoneModule.onEIDFrame(callback); + } + onURLFrame(callback: any): EventSubscription { + return EddystoneModule.onURLFrame(callback); + } + onTelemetryData(callback: any): EventSubscription { + return EddystoneModule.onTelemetryData(callback); + } + onEmptyFrame(callback: any): EventSubscription { + return EddystoneModule.onEmptyFrame(callback); + } + onStateChanged(callback: any): EventSubscription { + return EddystoneModule.onStateChanged(callback); + } +} + +export default new EddystoneManager(); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a0b5084 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,21 @@ +export interface BeaconData { + id: string, + uid: string, + rssi: number, + txPower: number +} + +export interface TelemetryData { + uid: string, + voltage: number, + temp: number +} + +export interface URLData { + uid: string, + url: string +} + + + +