expo-beacon 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +51 -43
- package/build/ExpoBeacon.types.d.ts +8 -0
- package/build/ExpoBeacon.types.d.ts.map +1 -1
- package/build/ExpoBeacon.types.js.map +1 -1
- package/build/ExpoBeaconModule.d.ts +2 -2
- package/build/ExpoBeaconModule.d.ts.map +1 -1
- package/build/ExpoBeaconModule.js.map +1 -1
- package/ios/ExpoBeaconModule.swift +56 -55
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +8 -0
- package/src/ExpoBeaconModule.ts +2 -0
|
@@ -176,7 +176,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
Function("pairBeacon") { identifier: String, uuid: String, major: Int, minor: Int ->
|
|
179
|
+
Function("pairBeacon") { identifier: String, uuid: String, major: Int, minor: Int, name: String? ->
|
|
180
180
|
// Validate UUID format
|
|
181
181
|
try {
|
|
182
182
|
java.util.UUID.fromString(uuid)
|
|
@@ -198,6 +198,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
198
198
|
put("uuid", uuid)
|
|
199
199
|
put("major", major)
|
|
200
200
|
put("minor", minor)
|
|
201
|
+
if (name != null) put("name", name)
|
|
201
202
|
}
|
|
202
203
|
beacons.put(newBeacon)
|
|
203
204
|
prefs.edit().putString(PREFS_KEY, beacons.toString()).apply()
|
|
@@ -212,16 +213,18 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
212
213
|
val beacons = loadPairedBeaconsJson()
|
|
213
214
|
(0 until beacons.length()).map { i ->
|
|
214
215
|
val b = beacons.getJSONObject(i)
|
|
215
|
-
|
|
216
|
-
"identifier"
|
|
217
|
-
"uuid"
|
|
218
|
-
"major"
|
|
219
|
-
"minor"
|
|
220
|
-
|
|
216
|
+
buildMap<String, Any?> {
|
|
217
|
+
put("identifier", b.getString("identifier"))
|
|
218
|
+
put("uuid", b.getString("uuid"))
|
|
219
|
+
put("major", b.getInt("major"))
|
|
220
|
+
put("minor", b.getInt("minor"))
|
|
221
|
+
val n = b.optString("name").takeIf { it.isNotEmpty() }
|
|
222
|
+
if (n != null) put("name", n)
|
|
223
|
+
}
|
|
221
224
|
}
|
|
222
225
|
}
|
|
223
226
|
|
|
224
|
-
Function("pairEddystone") { identifier: String, namespace: String, instance: String ->
|
|
227
|
+
Function("pairEddystone") { identifier: String, namespace: String, instance: String, name: String? ->
|
|
225
228
|
if (!namespace.matches(NAMESPACE_REGEX)) {
|
|
226
229
|
throw expo.modules.kotlin.exception.CodedException("INVALID_NAMESPACE", "Namespace must be 20 hex characters, got: $namespace", null)
|
|
227
230
|
}
|
|
@@ -236,6 +239,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
236
239
|
put("identifier", identifier)
|
|
237
240
|
put("namespace", namespace)
|
|
238
241
|
put("instance", instance)
|
|
242
|
+
if (name != null) put("name", name)
|
|
239
243
|
}
|
|
240
244
|
eddystones.put(newEddystone)
|
|
241
245
|
eddystonePrefs.edit().putString(EDDYSTONE_PREFS_KEY, eddystones.toString()).apply()
|
|
@@ -250,11 +254,13 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
250
254
|
val eddystones = loadPairedEddystonesJson()
|
|
251
255
|
(0 until eddystones.length()).map { i ->
|
|
252
256
|
val e = eddystones.getJSONObject(i)
|
|
253
|
-
|
|
254
|
-
"identifier"
|
|
255
|
-
"namespace"
|
|
256
|
-
"instance"
|
|
257
|
-
|
|
257
|
+
buildMap<String, Any?> {
|
|
258
|
+
put("identifier", e.getString("identifier"))
|
|
259
|
+
put("namespace", e.getString("namespace"))
|
|
260
|
+
put("instance", e.getString("instance"))
|
|
261
|
+
val n = e.optString("name").takeIf { it.isNotEmpty() }
|
|
262
|
+
if (n != null) put("name", n)
|
|
263
|
+
}
|
|
258
264
|
}
|
|
259
265
|
}
|
|
260
266
|
|
|
@@ -446,14 +452,15 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
446
452
|
if (isEddystoneBeacon(beacon)) {
|
|
447
453
|
sendEvent("onEddystoneFound", eddystoneBeaconToMap(beacon))
|
|
448
454
|
} else if (beacon.identifiers.size >= 3) {
|
|
449
|
-
sendEvent("onBeaconFound",
|
|
450
|
-
"uuid"
|
|
451
|
-
"major"
|
|
452
|
-
"minor"
|
|
453
|
-
"rssi"
|
|
454
|
-
"distance"
|
|
455
|
-
"txPower"
|
|
456
|
-
|
|
455
|
+
sendEvent("onBeaconFound", buildMap<String, Any?> {
|
|
456
|
+
put("uuid", beacon.id1.toString().uppercase())
|
|
457
|
+
put("major", beacon.id2.toInt())
|
|
458
|
+
put("minor", beacon.id3.toInt())
|
|
459
|
+
put("rssi", beacon.rssi)
|
|
460
|
+
put("distance", beacon.distance)
|
|
461
|
+
put("txPower", beacon.txPower)
|
|
462
|
+
beacon.bluetoothName?.let { put("name", it) }
|
|
463
|
+
})
|
|
457
464
|
}
|
|
458
465
|
}
|
|
459
466
|
}
|
|
@@ -475,14 +482,15 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
475
482
|
|
|
476
483
|
val results = synchronized(scanResults) {
|
|
477
484
|
val mapped = scanResults.distinctBy { "${it.id1}:${it.id2}:${it.id3}" }.map { beacon ->
|
|
478
|
-
|
|
479
|
-
"uuid"
|
|
480
|
-
"major"
|
|
481
|
-
"minor"
|
|
482
|
-
"rssi"
|
|
483
|
-
"distance"
|
|
484
|
-
"txPower"
|
|
485
|
-
|
|
485
|
+
buildMap<String, Any?> {
|
|
486
|
+
put("uuid", beacon.id1.toString().uppercase())
|
|
487
|
+
put("major", beacon.id2.toInt())
|
|
488
|
+
put("minor", beacon.id3.toInt())
|
|
489
|
+
put("rssi", beacon.rssi)
|
|
490
|
+
put("distance", beacon.distance)
|
|
491
|
+
put("txPower", beacon.txPower)
|
|
492
|
+
beacon.bluetoothName?.let { put("name", it) }
|
|
493
|
+
}
|
|
486
494
|
}
|
|
487
495
|
scanResults.clear()
|
|
488
496
|
mapped
|
|
@@ -516,24 +524,24 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
516
524
|
unbindIfIdle()
|
|
517
525
|
}
|
|
518
526
|
|
|
519
|
-
private fun eddystoneBeaconToMap(beacon: Beacon): Map<String, Any
|
|
527
|
+
private fun eddystoneBeaconToMap(beacon: Beacon): Map<String, Any?> {
|
|
520
528
|
// AltBeacon provides distance via its built-in path-loss model.
|
|
521
529
|
// iOS uses a custom calculateDistance() with NaN/Infinity clamping for Eddystone.
|
|
522
530
|
// Both return -1.0 for invalid readings, but distance estimates may differ slightly.
|
|
523
|
-
|
|
524
|
-
"rssi"
|
|
525
|
-
"distance"
|
|
526
|
-
"txPower"
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
531
|
+
return buildMap {
|
|
532
|
+
put("rssi", beacon.rssi)
|
|
533
|
+
put("distance", beacon.distance)
|
|
534
|
+
put("txPower", beacon.txPower)
|
|
535
|
+
if (beacon.identifiers.size >= 2) {
|
|
536
|
+
put("frameType", "uid")
|
|
537
|
+
put("namespace", beacon.id1.toString().removePrefix("0x"))
|
|
538
|
+
put("instance", beacon.id2.toString().removePrefix("0x"))
|
|
539
|
+
} else {
|
|
540
|
+
put("frameType", "url")
|
|
541
|
+
put("url", decodeEddystoneUrl(beacon.id1.toByteArray()))
|
|
542
|
+
}
|
|
543
|
+
beacon.bluetoothName?.let { put("name", it) }
|
|
535
544
|
}
|
|
536
|
-
return map
|
|
537
545
|
}
|
|
538
546
|
|
|
539
547
|
// Decodes an Eddystone-URL payload from AltBeacon's id1 byte array.
|
|
@@ -6,6 +6,8 @@ export type BeaconScanResult = {
|
|
|
6
6
|
rssi: number;
|
|
7
7
|
distance: number;
|
|
8
8
|
txPower: number;
|
|
9
|
+
/** BLE advertising device name. May be undefined on iOS (CoreLocation does not expose it for iBeacon). */
|
|
10
|
+
name?: string;
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
13
|
* A beacon that has been paired/registered for monitoring.
|
|
@@ -18,6 +20,8 @@ export type PairedBeacon = {
|
|
|
18
20
|
uuid: string;
|
|
19
21
|
major: number;
|
|
20
22
|
minor: number;
|
|
23
|
+
/** BLE advertising device name, if provided at pairing time. */
|
|
24
|
+
name?: string;
|
|
21
25
|
};
|
|
22
26
|
/** Payload for enter/exit region events. */
|
|
23
27
|
export type BeaconRegionEvent = {
|
|
@@ -118,6 +122,8 @@ export type EddystoneScanResult = {
|
|
|
118
122
|
rssi: number;
|
|
119
123
|
distance: number;
|
|
120
124
|
txPower: number;
|
|
125
|
+
/** BLE advertising device name. */
|
|
126
|
+
name?: string;
|
|
121
127
|
};
|
|
122
128
|
/**
|
|
123
129
|
* An Eddystone-UID beacon that has been paired/registered for monitoring.
|
|
@@ -131,6 +137,8 @@ export type PairedEddystone = {
|
|
|
131
137
|
namespace: string;
|
|
132
138
|
/** 6-byte instance ID as hex string (12 chars). */
|
|
133
139
|
instance: string;
|
|
140
|
+
/** BLE advertising device name, if provided at pairing time. */
|
|
141
|
+
name?: string;
|
|
134
142
|
};
|
|
135
143
|
/** Payload for Eddystone enter/exit region events. */
|
|
136
144
|
export type EddystoneRegionEvent = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeacon.types.d.ts","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoBeacon.types.d.ts","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeacon.types.js","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"","sourcesContent":["/** Raw beacon discovered during a scan. */\r\nexport type BeaconScanResult = {\r\n uuid: string; // iBeacon proximity UUID (uppercase, formatted)\r\n major: number; // iBeacon major value (0–65535)\r\n minor: number; // iBeacon minor value (0–65535)\r\n rssi: number; // Signal strength in dBm (negative number)\r\n distance: number; // Estimated distance in meters\r\n txPower: number; // Calibrated TX power\r\n};\r\n\r\n/**\r\n * A beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedBeacon = {\r\n identifier: string; // User-defined label (e.g. \"lobby-door\")\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n};\r\n\r\n/** Payload for enter/exit region events. */\r\nexport type BeaconRegionEvent = {\r\n identifier: string; // Matches PairedBeacon.identifier\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n};\r\n\r\n/** Payload for periodic distance update events during monitoring. */\r\nexport type BeaconDistanceEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n distance: number;\r\n};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /**\r\n * Distance in metres at which exit events fire (must be ≥ maxDistance).\r\n * Creates a hysteresis band between enter and exit thresholds to prevent\r\n * rapid toggling near the boundary.\r\n *\r\n * Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.\r\n * Only used when `maxDistance` is set.\r\n */\r\n exitDistance?: number;\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Eddystone frame type. */\r\nexport type EddystoneFrameType = \"uid\" | \"url\";\r\n\r\n/** Raw Eddystone beacon discovered during a scan. */\r\nexport type EddystoneScanResult = {\r\n frameType: EddystoneFrameType;\r\n /** 10-byte namespace ID as hex string (20 chars). Present for UID frames. */\r\n namespace?: string;\r\n /** 6-byte instance ID as hex string (12 chars). Present for UID frames. */\r\n instance?: string;\r\n /** Decoded URL. Present for URL frames. */\r\n url?: string;\r\n rssi: number;\r\n distance: number;\r\n txPower: number;\r\n};\r\n\r\n/**\r\n * An Eddystone-UID beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedEddystone = {\r\n identifier: string;\r\n /** 10-byte namespace ID as hex string (20 chars). */\r\n namespace: string;\r\n /** 6-byte instance ID as hex string (12 chars). */\r\n instance: string;\r\n};\r\n\r\n/** Payload for Eddystone enter/exit region events. */\r\nexport type EddystoneRegionEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n};\r\n\r\n/** Payload for periodic Eddystone distance update events during monitoring. */\r\nexport type EddystoneDistanceEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n distance: number;\r\n};\r\n\r\n/** Module event map. */\r\nexport type ExpoBeaconModuleEvents = {\r\n onBeaconEnter: (params: BeaconRegionEvent) => void;\r\n onBeaconExit: (params: BeaconRegionEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired continuously during a live scan as each iBeacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n /** Fired continuously during a live scan as each Eddystone beacon is detected. */\r\n onEddystoneFound: (params: EddystoneScanResult) => void;\r\n onEddystoneEnter: (params: EddystoneRegionEvent) => void;\r\n onEddystoneExit: (params: EddystoneRegionEvent) => void;\r\n onEddystoneDistance: (params: EddystoneDistanceEvent) => void;\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoBeacon.types.js","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"","sourcesContent":["/** Raw beacon discovered during a scan. */\r\nexport type BeaconScanResult = {\r\n uuid: string; // iBeacon proximity UUID (uppercase, formatted)\r\n major: number; // iBeacon major value (0–65535)\r\n minor: number; // iBeacon minor value (0–65535)\r\n rssi: number; // Signal strength in dBm (negative number)\r\n distance: number; // Estimated distance in meters\r\n txPower: number; // Calibrated TX power\r\n /** BLE advertising device name. May be undefined on iOS (CoreLocation does not expose it for iBeacon). */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * A beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedBeacon = {\r\n identifier: string; // User-defined label (e.g. \"lobby-door\")\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n};\r\n\r\n/** Payload for enter/exit region events. */\r\nexport type BeaconRegionEvent = {\r\n identifier: string; // Matches PairedBeacon.identifier\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n};\r\n\r\n/** Payload for periodic distance update events during monitoring. */\r\nexport type BeaconDistanceEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n distance: number;\r\n};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /**\r\n * Distance in metres at which exit events fire (must be ≥ maxDistance).\r\n * Creates a hysteresis band between enter and exit thresholds to prevent\r\n * rapid toggling near the boundary.\r\n *\r\n * Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.\r\n * Only used when `maxDistance` is set.\r\n */\r\n exitDistance?: number;\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Eddystone frame type. */\r\nexport type EddystoneFrameType = \"uid\" | \"url\";\r\n\r\n/** Raw Eddystone beacon discovered during a scan. */\r\nexport type EddystoneScanResult = {\r\n frameType: EddystoneFrameType;\r\n /** 10-byte namespace ID as hex string (20 chars). Present for UID frames. */\r\n namespace?: string;\r\n /** 6-byte instance ID as hex string (12 chars). Present for UID frames. */\r\n instance?: string;\r\n /** Decoded URL. Present for URL frames. */\r\n url?: string;\r\n rssi: number;\r\n distance: number;\r\n txPower: number;\r\n /** BLE advertising device name. */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * An Eddystone-UID beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedEddystone = {\r\n identifier: string;\r\n /** 10-byte namespace ID as hex string (20 chars). */\r\n namespace: string;\r\n /** 6-byte instance ID as hex string (12 chars). */\r\n instance: string;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n};\r\n\r\n/** Payload for Eddystone enter/exit region events. */\r\nexport type EddystoneRegionEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n};\r\n\r\n/** Payload for periodic Eddystone distance update events during monitoring. */\r\nexport type EddystoneDistanceEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n distance: number;\r\n};\r\n\r\n/** Module event map. */\r\nexport type ExpoBeaconModuleEvents = {\r\n onBeaconEnter: (params: BeaconRegionEvent) => void;\r\n onBeaconExit: (params: BeaconRegionEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired continuously during a live scan as each iBeacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n /** Fired continuously during a live scan as each Eddystone beacon is detected. */\r\n onEddystoneFound: (params: EddystoneScanResult) => void;\r\n onEddystoneEnter: (params: EddystoneRegionEvent) => void;\r\n onEddystoneExit: (params: EddystoneRegionEvent) => void;\r\n onEddystoneDistance: (params: EddystoneDistanceEvent) => void;\r\n};\r\n"]}
|
|
@@ -24,7 +24,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
24
24
|
/**
|
|
25
25
|
* Register a beacon for persistent region monitoring.
|
|
26
26
|
*/
|
|
27
|
-
pairBeacon(identifier: string, uuid: string, major: number, minor: number): void;
|
|
27
|
+
pairBeacon(identifier: string, uuid: string, major: number, minor: number, name?: string): void;
|
|
28
28
|
/**
|
|
29
29
|
* Remove a previously paired beacon.
|
|
30
30
|
*/
|
|
@@ -36,7 +36,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
36
36
|
/**
|
|
37
37
|
* Register an Eddystone-UID beacon for persistent monitoring.
|
|
38
38
|
*/
|
|
39
|
-
pairEddystone(identifier: string, namespace: string, instance: string): void;
|
|
39
|
+
pairEddystone(identifier: string, namespace: string, instance: string, name?: string): void;
|
|
40
40
|
/**
|
|
41
41
|
* Remove a previously paired Eddystone beacon.
|
|
42
42
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,OAAO,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IACzE;;;;;;;;;;;OAWG;IACH,mBAAmB,CACjB,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAE9B;;;;;OAKG;IACH,sBAAsB,CACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAEjC;;OAEG;IACH,UAAU,CACR,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,IAAI;IAEP;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEtC;;OAEG;IACH,gBAAgB,IAAI,YAAY,EAAE;IAElC;;OAEG;IACH,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,OAAO,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IACzE;;;;;;;;;;;OAWG;IACH,mBAAmB,CACjB,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAE9B;;;;;OAKG;IACH,sBAAsB,CACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAEjC;;OAEG;IACH,UAAU,CACR,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI;IAEP;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEtC;;OAEG;IACH,gBAAgB,IAAI,YAAY,EAAE;IAElC;;OAEG;IACH,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI;IAEP;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEzC;;OAEG;IACH,mBAAmB,IAAI,eAAe,EAAE;IAExC;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAEvD;;;;;;;OAOG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpE;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAE3B,iEAAiE;IACjE,kBAAkB,IAAI,IAAI;IAE1B;;;OAGG;IACH,UAAU,IAAI,IAAI;IAElB,yEAAyE;IACzE,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;CAC5C;;AAED,wBAAmE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAyHzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n EddystoneScanResult,\r\n PairedBeacon,\r\n PairedEddystone,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n} from \"./ExpoBeacon.types\";\r\n\r\ndeclare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {\r\n /**\r\n * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.\r\n *\r\n * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).\r\n * On iOS, at least one UUID is required — Apple strips iBeacon data from BLE\r\n * advertisements, making wildcard discovery impossible. When you pass an empty\r\n * array, the module automatically uses UUIDs from paired beacons.\r\n * On Android, pass an empty array to discover all nearby iBeacons.\r\n *\r\n * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(\r\n uuids?: string[],\r\n scanDuration?: number,\r\n ): Promise<BeaconScanResult[]>;\r\n\r\n /**\r\n * Start a one-shot Eddystone beacon scan using BLE.\r\n * Discovers Eddystone-UID and Eddystone-URL frames.\r\n *\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForEddystonesAsync(\r\n scanDuration?: number,\r\n ): Promise<EddystoneScanResult[]>;\r\n\r\n /**\r\n * Register a beacon for persistent region monitoring.\r\n */\r\n pairBeacon(\r\n identifier: string,\r\n uuid: string,\r\n major: number,\r\n minor: number,\r\n name?: string,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired beacon.\r\n */\r\n unpairBeacon(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired beacons.\r\n */\r\n getPairedBeacons(): PairedBeacon[];\r\n\r\n /**\r\n * Register an Eddystone-UID beacon for persistent monitoring.\r\n */\r\n pairEddystone(\r\n identifier: string,\r\n namespace: string,\r\n instance: string,\r\n name?: string,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired Eddystone beacon.\r\n */\r\n unpairEddystone(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired Eddystone beacons.\r\n */\r\n getPairedEddystones(): PairedEddystone[];\r\n\r\n /**\r\n * Set persistent notification configuration. Settings are saved and applied to all\r\n * subsequent monitoring sessions until explicitly changed.\r\n */\r\n setNotificationConfig(config: NotificationConfig): void;\r\n\r\n /**\r\n * Start background region monitoring for all paired beacons.\r\n * On Android starts a foreground service.\r\n * On iOS starts CLLocationManager region monitoring.\r\n *\r\n * Accepts a plain number (backward-compatible maxDistance shorthand) or a\r\n * MonitoringOptions object with maxDistance and/or notification overrides.\r\n */\r\n startMonitoring(options?: MonitoringOptions | number): Promise<void>;\r\n\r\n /**\r\n * Stop background region monitoring.\r\n */\r\n stopMonitoring(): Promise<void>;\r\n\r\n /**\r\n * Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.\r\n * Call stopContinuousScan() to end the scan.\r\n */\r\n startContinuousScan(): void;\r\n\r\n /** Stop the continuous scan started by startContinuousScan(). */\r\n stopContinuousScan(): void;\r\n\r\n /**\r\n * Cancel any in-progress one-shot scan (iBeacon or Eddystone).\r\n * The pending promise will be rejected with code \"SCAN_CANCELLED\".\r\n */\r\n cancelScan(): void;\r\n\r\n /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n}\r\n\r\nexport default requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n"]}
|
|
@@ -205,7 +205,7 @@ public class ExpoBeaconModule: Module {
|
|
|
205
205
|
|
|
206
206
|
// MARK: - Pair
|
|
207
207
|
|
|
208
|
-
Function("pairBeacon") { (identifier: String, uuid: String, major: Int, minor: Int) -> Void in
|
|
208
|
+
Function("pairBeacon") { (identifier: String, uuid: String, major: Int, minor: Int, name: String?) -> Void in
|
|
209
209
|
guard UUID(uuidString: uuid) != nil else {
|
|
210
210
|
throw Exception(name: "INVALID_UUID", description: "Invalid UUID format: \(uuid)")
|
|
211
211
|
}
|
|
@@ -218,12 +218,14 @@ public class ExpoBeaconModule: Module {
|
|
|
218
218
|
|
|
219
219
|
var beacons = self.loadPairedBeaconsRaw()
|
|
220
220
|
beacons.removeAll { ($0["identifier"] as? String) == identifier }
|
|
221
|
-
|
|
221
|
+
var entry: [String: Any] = [
|
|
222
222
|
"identifier": identifier,
|
|
223
223
|
"uuid": uuid,
|
|
224
224
|
"major": major,
|
|
225
225
|
"minor": minor
|
|
226
|
-
]
|
|
226
|
+
]
|
|
227
|
+
if let name = name { entry["name"] = name }
|
|
228
|
+
beacons.append(entry)
|
|
227
229
|
self.defaults.set(beacons, forKey: PAIRED_BEACONS_KEY)
|
|
228
230
|
self.cachedPairedBeacons = nil
|
|
229
231
|
}
|
|
@@ -241,7 +243,7 @@ public class ExpoBeaconModule: Module {
|
|
|
241
243
|
|
|
242
244
|
// MARK: - Eddystone Pair
|
|
243
245
|
|
|
244
|
-
Function("pairEddystone") { (identifier: String, namespace: String, instance: String) -> Void in
|
|
246
|
+
Function("pairEddystone") { (identifier: String, namespace: String, instance: String, name: String?) -> Void in
|
|
245
247
|
guard namespace.count == 20, namespace.range(of: "^[0-9a-fA-F]+$", options: .regularExpression) != nil else {
|
|
246
248
|
throw Exception(name: "INVALID_NAMESPACE", description: "Namespace must be 20 hex characters, got: \(namespace)")
|
|
247
249
|
}
|
|
@@ -253,11 +255,13 @@ public class ExpoBeaconModule: Module {
|
|
|
253
255
|
eddystones.removeAll { ($0["identifier"] as? String) == identifier }
|
|
254
256
|
// Normalize hex to lowercase — parseEddystoneFrame produces lowercase,
|
|
255
257
|
// so stored values must match for monitoring comparisons.
|
|
256
|
-
|
|
258
|
+
var entry: [String: Any] = [
|
|
257
259
|
"identifier": identifier,
|
|
258
260
|
"namespace": namespace.lowercased(),
|
|
259
261
|
"instance": instance.lowercased()
|
|
260
|
-
]
|
|
262
|
+
]
|
|
263
|
+
if let name = name { entry["name"] = name }
|
|
264
|
+
eddystones.append(entry)
|
|
261
265
|
self.defaults.set(eddystones, forKey: PAIRED_EDDYSTONES_KEY)
|
|
262
266
|
self.cachedPairedEddystones = nil
|
|
263
267
|
}
|
|
@@ -651,12 +655,18 @@ public class ExpoBeaconModule: Module {
|
|
|
651
655
|
|
|
652
656
|
guard let beacon = Self.parseEddystoneFrame(data: data, rssi: rssi.intValue) else { return }
|
|
653
657
|
|
|
658
|
+
// Augment with the BLE advertising device name if present
|
|
659
|
+
var beaconInfo = beacon
|
|
660
|
+
if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
|
|
661
|
+
beaconInfo["name"] = localName
|
|
662
|
+
}
|
|
663
|
+
|
|
654
664
|
if eddystoneScanPromise != nil {
|
|
655
|
-
eddystoneScannedBeacons.append(
|
|
665
|
+
eddystoneScannedBeacons.append(beaconInfo)
|
|
656
666
|
}
|
|
657
667
|
|
|
658
668
|
if continuousScanActive {
|
|
659
|
-
sendEvent("onEddystoneFound",
|
|
669
|
+
sendEvent("onEddystoneFound", beaconInfo)
|
|
660
670
|
}
|
|
661
671
|
|
|
662
672
|
// Eddystone monitoring: match UID frames against paired list
|
|
@@ -1054,28 +1064,30 @@ public class ExpoBeaconModule: Module {
|
|
|
1054
1064
|
// Emit distance event every ranging cycle (~1 s)
|
|
1055
1065
|
sendEvent("onBeaconDistance", makeBeaconEventParams(identifier: identifier, beacon: beacon))
|
|
1056
1066
|
|
|
1057
|
-
//
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1067
|
+
// Enter/exit synthesis with hysteresis — always applied.
|
|
1068
|
+
// When maxDistance is set, distance thresholds control transitions.
|
|
1069
|
+
// When maxDistance is nil, pure presence-based hysteresis is used
|
|
1070
|
+
// (HYSTERESIS_COUNT consecutive readings to confirm enter).
|
|
1071
|
+
let maxDist = self.defaults.object(forKey: MAX_DISTANCE_KEY) as? Double
|
|
1072
|
+
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
1073
|
+
let action = evaluateDistanceHysteresis(
|
|
1074
|
+
identifier: identifier,
|
|
1075
|
+
distance: beacon.accuracy,
|
|
1076
|
+
maxDistance: maxDist,
|
|
1077
|
+
exitDistance: exitDist,
|
|
1078
|
+
entered: &enteredRegions,
|
|
1079
|
+
enterCtrs: &enterCounters,
|
|
1080
|
+
exitCtrs: &exitCounters
|
|
1081
|
+
)
|
|
1082
|
+
switch action {
|
|
1083
|
+
case .enter:
|
|
1084
|
+
sendEvent("onBeaconEnter", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "enter"))
|
|
1085
|
+
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
1086
|
+
case .exit:
|
|
1087
|
+
sendEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "exit"))
|
|
1088
|
+
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1089
|
+
case .none:
|
|
1090
|
+
break
|
|
1079
1091
|
}
|
|
1080
1092
|
|
|
1081
1093
|
// Note: onBeaconFound for continuous scan is emitted by the
|
|
@@ -1119,38 +1131,27 @@ public class ExpoBeaconModule: Module {
|
|
|
1119
1131
|
}
|
|
1120
1132
|
|
|
1121
1133
|
fileprivate func handleDidEnterRegion(_ region: CLRegion) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
// If maxDistance is set, distance ranging handles enter/exit — skip region-based emit
|
|
1126
|
-
guard self.defaults.object(forKey: MAX_DISTANCE_KEY) == nil else { return }
|
|
1127
|
-
|
|
1128
|
-
sendEvent("onBeaconEnter", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "enter"))
|
|
1129
|
-
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
1134
|
+
// Region callbacks are suppressed — all enter/exit logic goes through
|
|
1135
|
+
// ranging-based hysteresis in handleDidRange for consistent behaviour
|
|
1136
|
+
// with HYSTERESIS_COUNT, regardless of whether maxDistance is set.
|
|
1130
1137
|
}
|
|
1131
1138
|
|
|
1132
1139
|
fileprivate func handleDidExitRegion(_ region: CLRegion) {
|
|
1133
1140
|
guard let beaconRegion = region as? CLBeaconRegion else { return }
|
|
1134
1141
|
let identifier = beaconRegion.identifier
|
|
1135
1142
|
|
|
1136
|
-
//
|
|
1137
|
-
//
|
|
1138
|
-
//
|
|
1139
|
-
//
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1148
|
-
}
|
|
1149
|
-
return
|
|
1143
|
+
// Ranging-based hysteresis (handleDidRange miss counter) handles exit
|
|
1144
|
+
// in most cases. However, when the OS fires didExitRegion, ranging may
|
|
1145
|
+
// have already stopped delivering callbacks for this beacon. If the
|
|
1146
|
+
// beacon was in "entered" state, emit the exit event as a safety net.
|
|
1147
|
+
let wasEntered = enteredRegions.remove(identifier) != nil
|
|
1148
|
+
enterCounters.removeValue(forKey: identifier)
|
|
1149
|
+
exitCounters.removeValue(forKey: identifier)
|
|
1150
|
+
missCounters.removeValue(forKey: identifier)
|
|
1151
|
+
if wasEntered {
|
|
1152
|
+
sendEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "exit"))
|
|
1153
|
+
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1150
1154
|
}
|
|
1151
|
-
|
|
1152
|
-
sendEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "exit"))
|
|
1153
|
-
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1154
1155
|
}
|
|
1155
1156
|
|
|
1156
1157
|
fileprivate func handleMonitoringDidFail(for region: CLRegion?, withError error: Error) {
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type BeaconScanResult = {
|
|
|
6
6
|
rssi: number; // Signal strength in dBm (negative number)
|
|
7
7
|
distance: number; // Estimated distance in meters
|
|
8
8
|
txPower: number; // Calibrated TX power
|
|
9
|
+
/** BLE advertising device name. May be undefined on iOS (CoreLocation does not expose it for iBeacon). */
|
|
10
|
+
name?: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -19,6 +21,8 @@ export type PairedBeacon = {
|
|
|
19
21
|
uuid: string;
|
|
20
22
|
major: number;
|
|
21
23
|
minor: number;
|
|
24
|
+
/** BLE advertising device name, if provided at pairing time. */
|
|
25
|
+
name?: string;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
/** Payload for enter/exit region events. */
|
|
@@ -128,6 +132,8 @@ export type EddystoneScanResult = {
|
|
|
128
132
|
rssi: number;
|
|
129
133
|
distance: number;
|
|
130
134
|
txPower: number;
|
|
135
|
+
/** BLE advertising device name. */
|
|
136
|
+
name?: string;
|
|
131
137
|
};
|
|
132
138
|
|
|
133
139
|
/**
|
|
@@ -142,6 +148,8 @@ export type PairedEddystone = {
|
|
|
142
148
|
namespace: string;
|
|
143
149
|
/** 6-byte instance ID as hex string (12 chars). */
|
|
144
150
|
instance: string;
|
|
151
|
+
/** BLE advertising device name, if provided at pairing time. */
|
|
152
|
+
name?: string;
|
|
145
153
|
};
|
|
146
154
|
|
|
147
155
|
/** Payload for Eddystone enter/exit region events. */
|
package/src/ExpoBeaconModule.ts
CHANGED
|
@@ -46,6 +46,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
46
46
|
uuid: string,
|
|
47
47
|
major: number,
|
|
48
48
|
minor: number,
|
|
49
|
+
name?: string,
|
|
49
50
|
): void;
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -65,6 +66,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
65
66
|
identifier: string,
|
|
66
67
|
namespace: string,
|
|
67
68
|
instance: string,
|
|
69
|
+
name?: string,
|
|
68
70
|
): void;
|
|
69
71
|
|
|
70
72
|
/**
|