expo-beacon 0.1.0 → 0.2.0
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/README.md +154 -13
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +68 -14
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +48 -4
- package/build/ExpoBeacon.types.d.ts +58 -0
- package/build/ExpoBeacon.types.d.ts.map +1 -1
- package/build/ExpoBeacon.types.js.map +1 -1
- package/build/ExpoBeaconModule.d.ts +1 -45
- package/build/ExpoBeaconModule.d.ts.map +1 -1
- package/build/ExpoBeaconModule.js +9 -1
- package/build/ExpoBeaconModule.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/ios/ExpoBeaconModule.swift +56 -5
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +63 -0
- package/src/ExpoBeaconModule.ts +23 -5
- package/src/index.ts +6 -0
package/README.md
CHANGED
|
@@ -276,28 +276,46 @@ paired.forEach((b) =>
|
|
|
276
276
|
|
|
277
277
|
---
|
|
278
278
|
|
|
279
|
-
### `startMonitoring(
|
|
279
|
+
### `startMonitoring(options?)`
|
|
280
280
|
|
|
281
281
|
```ts
|
|
282
|
-
startMonitoring(
|
|
282
|
+
startMonitoring(options?: MonitoringOptions | number): Promise<void>
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
Starts background region monitoring for all paired beacons.
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
Accepts either a `MonitoringOptions` object or a plain `number` (backward-compatible shorthand for `maxDistance`).
|
|
288
|
+
|
|
289
|
+
**`MonitoringOptions`**
|
|
290
|
+
|
|
291
|
+
| Property | Type | Default | Description |
|
|
292
|
+
| --------------- | -------------------- | ----------- | --------------------------------------------------------------------------------------------- |
|
|
293
|
+
| `maxDistance` | `number` | `undefined` | Optional distance threshold in metres. `onBeaconEnter` is only emitted when the measured distance is ≤ this value. `onBeaconExit` is always emitted. Omit to disable distance filtering. |
|
|
294
|
+
| `notifications` | `NotificationConfig` | `undefined` | Notification config overrides applied for this session. Persisted and takes effect immediately. |
|
|
290
295
|
|
|
291
296
|
**Android**: Launches `BeaconForegroundService` — a persistent foreground service required by Android 8+ for background BLE. Restarts automatically after device reboot.
|
|
292
297
|
|
|
293
298
|
**iOS**: Activates `CLLocationManager` region monitoring. iOS can wake or relaunch the app when a region boundary is crossed, even if the app is terminated.
|
|
294
299
|
|
|
295
300
|
```ts
|
|
296
|
-
//
|
|
297
|
-
await ExpoBeacon.startMonitoring();
|
|
298
|
-
|
|
299
|
-
// Only fire enter events when within 5 metres
|
|
301
|
+
// Backward-compatible shorthand (number = maxDistance)
|
|
300
302
|
await ExpoBeacon.startMonitoring(5);
|
|
303
|
+
|
|
304
|
+
// Full options object
|
|
305
|
+
await ExpoBeacon.startMonitoring({
|
|
306
|
+
maxDistance: 5,
|
|
307
|
+
notifications: {
|
|
308
|
+
beaconEvents: {
|
|
309
|
+
enterTitle: "Beacon nearby!",
|
|
310
|
+
body: "{identifier} is within range",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Monitor with no distance limit and no enter/exit notifications
|
|
316
|
+
await ExpoBeacon.startMonitoring({
|
|
317
|
+
notifications: { beaconEvents: { enabled: false } },
|
|
318
|
+
});
|
|
301
319
|
```
|
|
302
320
|
|
|
303
321
|
> Call `requestPermissionsAsync()` before `startMonitoring()`.
|
|
@@ -318,6 +336,48 @@ await ExpoBeacon.stopMonitoring();
|
|
|
318
336
|
|
|
319
337
|
---
|
|
320
338
|
|
|
339
|
+
### `setNotificationConfig(config)`
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
setNotificationConfig(config: NotificationConfig): void
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Persists notification configuration that is applied to all subsequent monitoring sessions. Values are stored in `SharedPreferences` (Android) / `UserDefaults` (iOS) and survive app restarts.
|
|
346
|
+
|
|
347
|
+
For one-off overrides tied to a single session, pass `notifications` directly in [`startMonitoring(options)`](#startmonitoringoptions) instead — it calls this function automatically.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
ExpoBeacon.setNotificationConfig({
|
|
351
|
+
// Enter/exit alert notifications
|
|
352
|
+
beaconEvents: {
|
|
353
|
+
enabled: true, // false to suppress all enter/exit notifications
|
|
354
|
+
enterTitle: "Beacon nearby",
|
|
355
|
+
exitTitle: "Beacon out of range",
|
|
356
|
+
body: "{identifier} {event}ed", // {identifier} and {event} are replaced at runtime
|
|
357
|
+
sound: true, // iOS only — default true
|
|
358
|
+
icon: "ic_beacon_notification", // Android only — drawable resource name
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
// Persistent status-bar notification while monitoring is active (Android only)
|
|
362
|
+
foregroundService: {
|
|
363
|
+
title: "My App is watching",
|
|
364
|
+
text: "Monitoring for nearby beacons",
|
|
365
|
+
icon: "ic_service", // Android drawable resource name
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// Android notification channel settings
|
|
369
|
+
channel: {
|
|
370
|
+
name: "Proximity Alerts",
|
|
371
|
+
description: "Alerts when beacons enter or leave range",
|
|
372
|
+
importance: "default", // "low" | "default" | "high"
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
> **Android channel importance note**: Android prevents decreasing channel importance once a user has been notified. Increasing importance always works; decreasing it will have no effect until the user clears the app's notification settings or reinstalls the app.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
321
381
|
## Events
|
|
322
382
|
|
|
323
383
|
Subscribe to events using `ExpoBeacon.addListener(eventName, handler)`. Always call `.remove()` on the subscription when your component unmounts.
|
|
@@ -476,6 +536,71 @@ type BeaconDistanceEvent = {
|
|
|
476
536
|
|
|
477
537
|
---
|
|
478
538
|
|
|
539
|
+
### `MonitoringOptions`
|
|
540
|
+
|
|
541
|
+
Passed to `startMonitoring(options)`.
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
type MonitoringOptions = {
|
|
545
|
+
maxDistance?: number; // Distance threshold in metres for enter events
|
|
546
|
+
notifications?: NotificationConfig; // Notification overrides for this session
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### `NotificationConfig`
|
|
551
|
+
|
|
552
|
+
Top-level config object accepted by `setNotificationConfig()` and `startMonitoring({ notifications })`.
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
type NotificationConfig = {
|
|
556
|
+
beaconEvents?: BeaconNotificationConfig;
|
|
557
|
+
foregroundService?: ForegroundServiceConfig; // Android only
|
|
558
|
+
channel?: NotificationChannelConfig; // Android only
|
|
559
|
+
};
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### `BeaconNotificationConfig`
|
|
563
|
+
|
|
564
|
+
Controls the enter/exit alert notifications.
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
type BeaconNotificationConfig = {
|
|
568
|
+
enabled?: boolean; // false to disable all enter/exit notifications. Default: true
|
|
569
|
+
enterTitle?: string; // Default: "Beacon Entered"
|
|
570
|
+
exitTitle?: string; // Default: "Beacon Exited"
|
|
571
|
+
body?: string; // Template — {identifier} and {event} are replaced at runtime
|
|
572
|
+
// Default: "{identifier} region {event}ed"
|
|
573
|
+
sound?: boolean; // iOS only — play notification sound. Default: true
|
|
574
|
+
icon?: string; // Android only — drawable resource name (e.g. "ic_notification")
|
|
575
|
+
};
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### `ForegroundServiceConfig`
|
|
579
|
+
|
|
580
|
+
Controls the persistent Android status-bar notification while monitoring is active.
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
type ForegroundServiceConfig = {
|
|
584
|
+
title?: string; // Default: "Beacon Monitoring Active"
|
|
585
|
+
text?: string; // Default: "Monitoring for iBeacons in the background"
|
|
586
|
+
icon?: string; // Android drawable resource name
|
|
587
|
+
};
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### `NotificationChannelConfig`
|
|
591
|
+
|
|
592
|
+
Controls the Android notification channel shown in system settings.
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
type NotificationChannelConfig = {
|
|
596
|
+
name?: string; // Default: "Beacon Monitoring"
|
|
597
|
+
description?: string; // Default: "Used for background iBeacon region monitoring"
|
|
598
|
+
importance?: "low" | "default" | "high"; // Default: "low"
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
479
604
|
## Background Behaviour
|
|
480
605
|
|
|
481
606
|
### Android
|
|
@@ -494,14 +619,30 @@ Default scan timing: 1.1 s scan window every 5 s.
|
|
|
494
619
|
|
|
495
620
|
## Notifications
|
|
496
621
|
|
|
497
|
-
A local notification is posted for every `onBeaconEnter` and `onBeaconExit` event.
|
|
622
|
+
A local notification is posted for every `onBeaconEnter` and `onBeaconExit` event. All notification settings can be customised via [`setNotificationConfig()`](#setnotificationconfigconfig) or inline in [`startMonitoring(options)`](#startmonitoringoptions).
|
|
623
|
+
|
|
624
|
+
### Defaults
|
|
625
|
+
|
|
626
|
+
| Property | Default value |
|
|
627
|
+
| ------------------------------ | ------------------------------------------------ |
|
|
628
|
+
| Enter title | `"Beacon Entered"` |
|
|
629
|
+
| Exit title | `"Beacon Exited"` |
|
|
630
|
+
| Body | `"{identifier} region {event}ed"` |
|
|
631
|
+
| Sound (iOS) | `true` |
|
|
632
|
+
| Icon (Android) | System `ic_dialog_info` |
|
|
633
|
+
| Foreground service title | `"Beacon Monitoring Active"` |
|
|
634
|
+
| Foreground service text | `"Monitoring for iBeacons in the background"` |
|
|
635
|
+
| Channel name (Android) | `"Beacon Monitoring"` |
|
|
636
|
+
| Channel importance (Android) | `"low"` |
|
|
637
|
+
|
|
638
|
+
### Channel IDs (Android)
|
|
498
639
|
|
|
499
640
|
| Channel / type | Importance |
|
|
500
641
|
| ------------------------------- | ------------------- |
|
|
501
|
-
| Foreground service (Android) | `
|
|
502
|
-
| Enter / exit alerts | `
|
|
642
|
+
| Foreground service (Android) | configurable (default `low`) |
|
|
643
|
+
| Enter / exit alerts | configurable (default `default`) |
|
|
503
644
|
|
|
504
|
-
Both
|
|
645
|
+
Both notifications share the channel id `expo_beacon_channel`. The channel is recreated on each `onStartCommand` so config changes take effect on the next monitoring start.
|
|
505
646
|
|
|
506
647
|
---
|
|
507
648
|
|
|
@@ -70,7 +70,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
70
70
|
)
|
|
71
71
|
}
|
|
72
72
|
// Use continuous scanning (not JobScheduler) for foreground service
|
|
73
|
-
|
|
73
|
+
// Guard: throws if called after ranging/monitoring has already started
|
|
74
|
+
try { manager.setEnableScheduledScanJobs(false) } catch (_: IllegalStateException) {}
|
|
74
75
|
manager.setBackgroundBetweenScanPeriod(5000L) // 5s between scans
|
|
75
76
|
manager.setBackgroundScanPeriod(1100L) // 1.1s scan window
|
|
76
77
|
manager.setForegroundScanPeriod(1000L) // 1s scan window for distance logging
|
|
@@ -231,11 +232,33 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
private fun showEnterExitNotification(region: Region, eventType: String) {
|
|
234
|
-
val
|
|
235
|
-
val
|
|
235
|
+
val config = readNotificationConfig()
|
|
236
|
+
val eventsConfig = config.optJSONObject("beaconEvents")
|
|
237
|
+
|
|
238
|
+
// Respect the enabled flag (defaults to true)
|
|
239
|
+
if (eventsConfig != null && !eventsConfig.optBoolean("enabled", true)) return
|
|
240
|
+
|
|
241
|
+
val defaultTitle = if (eventType == "enter") "Beacon Entered" else "Beacon Exited"
|
|
242
|
+
val title = if (eventType == "enter") {
|
|
243
|
+
eventsConfig?.optString("enterTitle")?.takeIf { it.isNotEmpty() } ?: defaultTitle
|
|
244
|
+
} else {
|
|
245
|
+
eventsConfig?.optString("exitTitle")?.takeIf { it.isNotEmpty() } ?: defaultTitle
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
val bodyTemplate = eventsConfig?.optString("body")?.takeIf { it.isNotEmpty() }
|
|
249
|
+
?: "{identifier} region {event}ed"
|
|
250
|
+
val message = bodyTemplate
|
|
251
|
+
.replace("{identifier}", region.uniqueId)
|
|
252
|
+
.replace("{event}", eventType)
|
|
253
|
+
|
|
254
|
+
val iconName = eventsConfig?.optString("icon")?.takeIf { it.isNotEmpty() }
|
|
255
|
+
val iconResId = iconName?.let { name ->
|
|
256
|
+
try { resources.getIdentifier(name, "drawable", packageName).takeIf { it != 0 } }
|
|
257
|
+
catch (_: Exception) { null }
|
|
258
|
+
} ?: android.R.drawable.ic_dialog_info
|
|
236
259
|
|
|
237
260
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
|
238
|
-
.setSmallIcon(
|
|
261
|
+
.setSmallIcon(iconResId)
|
|
239
262
|
.setContentTitle(title)
|
|
240
263
|
.setContentText(message)
|
|
241
264
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
@@ -251,27 +274,58 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
251
274
|
|
|
252
275
|
private fun createNotificationChannel() {
|
|
253
276
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
254
|
-
val
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
277
|
+
val config = readNotificationConfig()
|
|
278
|
+
val channelConfig = config.optJSONObject("channel")
|
|
279
|
+
|
|
280
|
+
val channelName = channelConfig?.optString("name")?.takeIf { it.isNotEmpty() }
|
|
281
|
+
?: "Beacon Monitoring"
|
|
282
|
+
val channelDesc = channelConfig?.optString("description")?.takeIf { it.isNotEmpty() }
|
|
283
|
+
?: "Used for background iBeacon region monitoring"
|
|
284
|
+
val importance = when (channelConfig?.optString("importance")) {
|
|
285
|
+
"high" -> NotificationManager.IMPORTANCE_HIGH
|
|
286
|
+
"default" -> NotificationManager.IMPORTANCE_DEFAULT
|
|
287
|
+
else -> NotificationManager.IMPORTANCE_LOW
|
|
260
288
|
}
|
|
261
|
-
|
|
289
|
+
|
|
290
|
+
val notifMgr = getSystemService(NotificationManager::class.java)
|
|
291
|
+
// Delete and recreate so config changes take effect
|
|
292
|
+
notifMgr?.deleteNotificationChannel(CHANNEL_ID)
|
|
293
|
+
val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
|
|
294
|
+
description = channelDesc
|
|
295
|
+
}
|
|
296
|
+
notifMgr?.createNotificationChannel(channel)
|
|
262
297
|
}
|
|
263
298
|
}
|
|
264
299
|
|
|
265
300
|
private fun buildForegroundNotification(): Notification {
|
|
301
|
+
val config = readNotificationConfig()
|
|
302
|
+
val fgConfig = config.optJSONObject("foregroundService")
|
|
303
|
+
|
|
304
|
+
val title = fgConfig?.optString("title")?.takeIf { it.isNotEmpty() }
|
|
305
|
+
?: "Beacon Monitoring Active"
|
|
306
|
+
val text = fgConfig?.optString("text")?.takeIf { it.isNotEmpty() }
|
|
307
|
+
?: "Monitoring for iBeacons in the background"
|
|
308
|
+
val iconName = fgConfig?.optString("icon")?.takeIf { it.isNotEmpty() }
|
|
309
|
+
val iconResId = iconName?.let { name ->
|
|
310
|
+
try { resources.getIdentifier(name, "drawable", packageName).takeIf { it != 0 } }
|
|
311
|
+
catch (_: Exception) { null }
|
|
312
|
+
} ?: android.R.drawable.ic_dialog_info
|
|
313
|
+
|
|
266
314
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
267
|
-
.setSmallIcon(
|
|
268
|
-
.setContentTitle(
|
|
269
|
-
.setContentText(
|
|
315
|
+
.setSmallIcon(iconResId)
|
|
316
|
+
.setContentTitle(title)
|
|
317
|
+
.setContentText(text)
|
|
270
318
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
271
319
|
.setOngoing(true)
|
|
272
320
|
.build()
|
|
273
321
|
}
|
|
274
322
|
|
|
323
|
+
private fun readNotificationConfig(): org.json.JSONObject {
|
|
324
|
+
val json = getSharedPreferences("expo.beacon.notification_config", Context.MODE_PRIVATE)
|
|
325
|
+
.getString("config", null) ?: return org.json.JSONObject()
|
|
326
|
+
return try { org.json.JSONObject(json) } catch (_: Exception) { org.json.JSONObject() }
|
|
327
|
+
}
|
|
328
|
+
|
|
275
329
|
override fun onDestroy() {
|
|
276
330
|
beaconManager.removeMonitorNotifier(monitorNotifier)
|
|
277
331
|
beaconManager.removeRangeNotifier(rangeNotifier)
|
|
@@ -21,15 +21,20 @@ import org.json.JSONObject
|
|
|
21
21
|
|
|
22
22
|
private const val PREFS_NAME = "expo.beacon.paired"
|
|
23
23
|
private const val PREFS_KEY = "paired_beacons"
|
|
24
|
+
private const val NOTIFICATION_CONFIG_PREFS = "expo.beacon.notification_config"
|
|
24
25
|
|
|
25
26
|
class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
26
27
|
|
|
27
28
|
private val beaconManager: BeaconManager by lazy {
|
|
28
29
|
BeaconManager.getInstanceForApplication(appContext.reactContext!!).also { manager ->
|
|
30
|
+
// Must be configured before any bind/ranging starts
|
|
31
|
+
manager.setEnableScheduledScanJobs(false)
|
|
29
32
|
// Register iBeacon layout parser
|
|
30
|
-
manager.beaconParsers.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
if (manager.beaconParsers.none { it.layout?.contains("0215") == true }) {
|
|
34
|
+
manager.beaconParsers.add(
|
|
35
|
+
BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
|
|
36
|
+
)
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -149,8 +154,23 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
149
154
|
}
|
|
150
155
|
}
|
|
151
156
|
|
|
152
|
-
AsyncFunction("startMonitoring") {
|
|
157
|
+
AsyncFunction("startMonitoring") { options: Any?, promise: Promise ->
|
|
153
158
|
val ctx = appContext.reactContext!!
|
|
159
|
+
var maxDistance: Double? = null
|
|
160
|
+
when (options) {
|
|
161
|
+
is Double -> maxDistance = options
|
|
162
|
+
is Map<*, *> -> {
|
|
163
|
+
@Suppress("UNCHECKED_CAST")
|
|
164
|
+
val map = options as Map<String, Any?>
|
|
165
|
+
maxDistance = (map["maxDistance"] as? Number)?.toDouble()
|
|
166
|
+
val notifications = map["notifications"]
|
|
167
|
+
if (notifications is Map<*, *>) {
|
|
168
|
+
@Suppress("UNCHECKED_CAST")
|
|
169
|
+
ctx.getSharedPreferences(NOTIFICATION_CONFIG_PREFS, Context.MODE_PRIVATE)
|
|
170
|
+
.edit().putString("config", mapToJson(notifications as Map<String, Any?>).toString()).apply()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
154
174
|
ctx.getSharedPreferences("expo.beacon.monitoring_options", Context.MODE_PRIVATE)
|
|
155
175
|
.edit().apply {
|
|
156
176
|
if (maxDistance != null) putFloat("max_distance", maxDistance.toFloat())
|
|
@@ -161,6 +181,13 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
161
181
|
promise.resolve(null)
|
|
162
182
|
}
|
|
163
183
|
|
|
184
|
+
Function("setNotificationConfig") { config: Map<String, Any?> ->
|
|
185
|
+
val ctx = appContext.reactContext!!
|
|
186
|
+
ctx.getSharedPreferences(NOTIFICATION_CONFIG_PREFS, Context.MODE_PRIVATE)
|
|
187
|
+
.edit().putString("config", mapToJson(config).toString()).apply()
|
|
188
|
+
null
|
|
189
|
+
}
|
|
190
|
+
|
|
164
191
|
AsyncFunction("stopMonitoring") { promise: Promise ->
|
|
165
192
|
BeaconForegroundService.stop(appContext.reactContext!!)
|
|
166
193
|
unregisterEventReceiver()
|
|
@@ -284,6 +311,23 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
284
311
|
scanPromise = null
|
|
285
312
|
}
|
|
286
313
|
|
|
314
|
+
// --- Notification config helpers ---
|
|
315
|
+
|
|
316
|
+
private fun mapToJson(map: Map<String, Any?>): JSONObject {
|
|
317
|
+
val json = JSONObject()
|
|
318
|
+
map.forEach { (key, value) ->
|
|
319
|
+
when (value) {
|
|
320
|
+
null -> json.put(key, JSONObject.NULL)
|
|
321
|
+
is Map<*, *> -> {
|
|
322
|
+
@Suppress("UNCHECKED_CAST")
|
|
323
|
+
json.put(key, mapToJson(value as Map<String, Any?>))
|
|
324
|
+
}
|
|
325
|
+
else -> json.put(key, value)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return json
|
|
329
|
+
}
|
|
330
|
+
|
|
287
331
|
// --- Shared Preferences helpers ---
|
|
288
332
|
|
|
289
333
|
private fun loadPairedBeaconsJson(): JSONArray {
|
|
@@ -41,6 +41,64 @@ export type BeaconDistanceEvent = {
|
|
|
41
41
|
minor: number;
|
|
42
42
|
distance: number;
|
|
43
43
|
};
|
|
44
|
+
/** Configuration for beacon enter/exit event notifications. */
|
|
45
|
+
export type BeaconNotificationConfig = {
|
|
46
|
+
/** Whether to show enter/exit notifications. Default: true. */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
/** Notification title on beacon enter. Default: "Beacon Entered". */
|
|
49
|
+
enterTitle?: string;
|
|
50
|
+
/** Notification title on beacon exit. Default: "Beacon Exited". */
|
|
51
|
+
exitTitle?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Notification body template. Supports {identifier} and {event} placeholders.
|
|
54
|
+
* Default: "{identifier} region {event}ed".
|
|
55
|
+
*/
|
|
56
|
+
body?: string;
|
|
57
|
+
/** Play a sound with the notification (iOS only). Default: true. */
|
|
58
|
+
sound?: boolean;
|
|
59
|
+
/** Android drawable resource name for the notification icon (e.g. "ic_notification"). */
|
|
60
|
+
icon?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Configuration for the Android foreground service notification (persistent status bar entry). */
|
|
63
|
+
export type ForegroundServiceConfig = {
|
|
64
|
+
/** Title of the persistent notification. Default: "Beacon Monitoring Active". */
|
|
65
|
+
title?: string;
|
|
66
|
+
/** Body text of the persistent notification. Default: "Monitoring for iBeacons in the background". */
|
|
67
|
+
text?: string;
|
|
68
|
+
/** Android drawable resource name for the notification icon. */
|
|
69
|
+
icon?: string;
|
|
70
|
+
};
|
|
71
|
+
/** Configuration for the Android notification channel. */
|
|
72
|
+
export type NotificationChannelConfig = {
|
|
73
|
+
/** Channel display name shown in system settings. Default: "Beacon Monitoring". */
|
|
74
|
+
name?: string;
|
|
75
|
+
/** Channel description shown in system settings. Default: "Used for background iBeacon region monitoring". */
|
|
76
|
+
description?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Channel importance level. Default: 'low'.
|
|
79
|
+
* Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.
|
|
80
|
+
*/
|
|
81
|
+
importance?: "low" | "default" | "high";
|
|
82
|
+
};
|
|
83
|
+
/** Combined notification configuration for all notification types. */
|
|
84
|
+
export type NotificationConfig = {
|
|
85
|
+
/** Settings for beacon enter/exit event notifications. */
|
|
86
|
+
beaconEvents?: BeaconNotificationConfig;
|
|
87
|
+
/** Settings for the persistent foreground service notification (Android only). */
|
|
88
|
+
foregroundService?: ForegroundServiceConfig;
|
|
89
|
+
/** Settings for the Android notification channel (Android only). */
|
|
90
|
+
channel?: NotificationChannelConfig;
|
|
91
|
+
};
|
|
92
|
+
/** Options accepted by startMonitoring(). */
|
|
93
|
+
export type MonitoringOptions = {
|
|
94
|
+
/**
|
|
95
|
+
* Maximum distance in metres for distance-based enter events.
|
|
96
|
+
* Exit events are always emitted when the region is lost.
|
|
97
|
+
*/
|
|
98
|
+
maxDistance?: number;
|
|
99
|
+
/** Notification configuration overrides to apply for this monitoring session. */
|
|
100
|
+
notifications?: NotificationConfig;
|
|
101
|
+
};
|
|
44
102
|
/** Module event map. */
|
|
45
103
|
export type ExpoBeaconModuleEvents = {
|
|
46
104
|
onBeaconEnter: (params: BeaconRegionEvent) => void;
|
|
@@ -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;CACjB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAwB;AACxB,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,eAAe,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACtD,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,wEAAwE;IACxE,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD,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/** A beacon that has been paired/registered for monitoring. */\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 ranging events (beacon proximity update). */\r\nexport type BeaconRangingEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n rssi: number;\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/** Module event map. */\r\nexport type ExpoBeaconModuleEvents = {\r\n onBeaconEnter: (params: BeaconRegionEvent) => void;\r\n onBeaconExit: (params: BeaconRegionEvent) => void;\r\n onBeaconRanging: (params: BeaconRangingEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired continuously during a live scan as each beacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => 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};\r\n\r\n/** A beacon that has been paired/registered for monitoring. */\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 ranging events (beacon proximity update). */\r\nexport type BeaconRangingEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n rssi: number;\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 /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\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 onBeaconRanging: (params: BeaconRangingEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired continuously during a live scan as each beacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n};\r\n"]}
|
|
@@ -1,46 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { ExpoBeaconModuleEvents, BeaconScanResult, PairedBeacon } from "./ExpoBeacon.types";
|
|
3
|
-
declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
4
|
-
/**
|
|
5
|
-
* Start a one-shot BLE scan. Resolves with discovered beacons after scanDuration ms.
|
|
6
|
-
* @param scanDuration Duration in ms (default 5000)
|
|
7
|
-
*/
|
|
8
|
-
scanForBeaconsAsync(scanDuration?: number): Promise<BeaconScanResult[]>;
|
|
9
|
-
/**
|
|
10
|
-
* Register a beacon for persistent region monitoring.
|
|
11
|
-
*/
|
|
12
|
-
pairBeacon(identifier: string, uuid: string, major: number, minor: number): void;
|
|
13
|
-
/**
|
|
14
|
-
* Remove a previously paired beacon.
|
|
15
|
-
*/
|
|
16
|
-
unpairBeacon(identifier: string): void;
|
|
17
|
-
/**
|
|
18
|
-
* Return all currently paired beacons.
|
|
19
|
-
*/
|
|
20
|
-
getPairedBeacons(): PairedBeacon[];
|
|
21
|
-
/**
|
|
22
|
-
* Start background region monitoring for all paired beacons.
|
|
23
|
-
* On Android starts a foreground service.
|
|
24
|
-
* On iOS starts CLLocationManager region monitoring.
|
|
25
|
-
* @param maxDistance Optional distance threshold in metres. Enter events are only
|
|
26
|
-
* emitted when the beacon is measured to be within this distance.
|
|
27
|
-
* Exit events are always emitted when the beacon region is lost.
|
|
28
|
-
*/
|
|
29
|
-
startMonitoring(maxDistance?: number): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* Stop background region monitoring.
|
|
32
|
-
*/
|
|
33
|
-
stopMonitoring(): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.
|
|
36
|
-
* Call stopContinuousScan() to end the scan.
|
|
37
|
-
*/
|
|
38
|
-
startContinuousScan(): void;
|
|
39
|
-
/** Stop the continuous scan started by startContinuousScan(). */
|
|
40
|
-
stopContinuousScan(): void;
|
|
41
|
-
/** Request Bluetooth + Location permissions. Returns true if granted. */
|
|
42
|
-
requestPermissionsAsync(): Promise<boolean>;
|
|
43
|
-
}
|
|
44
|
-
declare const _default: ExpoBeaconModule;
|
|
45
|
-
export default _default;
|
|
1
|
+
export default module;
|
|
46
2
|
//# sourceMappingURL=ExpoBeaconModule.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAiFA,eAAe,MAAM,CAAC"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { requireNativeModule } from "expo";
|
|
2
|
-
|
|
2
|
+
try {
|
|
3
|
+
// eslint-disable-next-line import/no-mutable-exports
|
|
4
|
+
var module = requireNativeModule("ExpoBeacon");
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
throw new Error("expo-beacon: native module not found. Make sure you are using a development build " +
|
|
8
|
+
"(not Expo Go) and have run `npx expo prebuild` followed by a native rebuild.");
|
|
9
|
+
}
|
|
10
|
+
export default module;
|
|
3
11
|
//# sourceMappingURL=ExpoBeaconModule.js.map
|
|
@@ -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;AAuEzD,IAAI,CAAC;IACH,qDAAqD;IACrD,IAAI,MAAM,GAAG,mBAAmB,CAAmB,YAAY,CAAC,CAAC;AACnE,CAAC;AAAC,MAAM,CAAC;IACP,MAAM,IAAI,KAAK,CACb,oFAAoF;QAClF,8EAA8E,CACjF,CAAC;AACJ,CAAC;AAED,eAAe,MAAM,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n PairedBeacon,\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 BLE scan. Resolves with discovered beacons after scanDuration ms.\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(scanDuration?: number): Promise<BeaconScanResult[]>;\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 ): 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 * 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 /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n}\r\n\r\ntry {\r\n // eslint-disable-next-line import/no-mutable-exports\r\n var module = requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n} catch {\r\n throw new Error(\r\n \"expo-beacon: native module not found. Make sure you are using a development build \" +\r\n \"(not Expo Go) and have run `npx expo prebuild` followed by a native rebuild.\",\r\n );\r\n}\r\n\r\nexport default module;\r\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default } from "./ExpoBeaconModule";
|
|
2
|
-
export type { BeaconScanResult, PairedBeacon, BeaconRegionEvent, BeaconRangingEvent, ExpoBeaconModuleEvents, } from "./ExpoBeacon.types";
|
|
2
|
+
export type { BeaconScanResult, PairedBeacon, BeaconRegionEvent, BeaconRangingEvent, BeaconDistanceEvent, ExpoBeaconModuleEvents, NotificationConfig, MonitoringOptions, BeaconNotificationConfig, ForegroundServiceConfig, NotificationChannelConfig, } from "./ExpoBeacon.types";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["// Native module (default export)\r\nexport { default } from \"./ExpoBeaconModule\";\r\n\r\n// All public types\r\nexport type {\r\n BeaconScanResult,\r\n PairedBeacon,\r\n BeaconRegionEvent,\r\n BeaconRangingEvent,\r\n ExpoBeaconModuleEvents,\r\n} from \"./ExpoBeacon.types\";\r\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["// Native module (default export)\r\nexport { default } from \"./ExpoBeaconModule\";\r\n\r\n// All public types\r\nexport type {\r\n BeaconScanResult,\r\n PairedBeacon,\r\n BeaconRegionEvent,\r\n BeaconRangingEvent,\r\n BeaconDistanceEvent,\r\n ExpoBeaconModuleEvents,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n BeaconNotificationConfig,\r\n ForegroundServiceConfig,\r\n NotificationChannelConfig,\r\n} from \"./ExpoBeacon.types\";\r\n"]}
|
|
@@ -5,8 +5,9 @@ import UserNotifications
|
|
|
5
5
|
private let PAIRED_BEACONS_KEY = "expo.beacon.paired"
|
|
6
6
|
private let IS_MONITORING_KEY = "expo.beacon.is_monitoring"
|
|
7
7
|
private let MAX_DISTANCE_KEY = "expo.beacon.max_distance"
|
|
8
|
+
private let NOTIFICATION_CONFIG_KEY = "expo.beacon.notification_config"
|
|
8
9
|
|
|
9
|
-
public class ExpoBeaconModule: Module, CLLocationManagerDelegate {
|
|
10
|
+
public class ExpoBeaconModule: NSObject, Module, CLLocationManagerDelegate {
|
|
10
11
|
|
|
11
12
|
private lazy var locationManager: CLLocationManager = {
|
|
12
13
|
let manager = CLLocationManager()
|
|
@@ -97,9 +98,29 @@ public class ExpoBeaconModule: Module, CLLocationManagerDelegate {
|
|
|
97
98
|
return self.loadPairedBeaconsRaw()
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// MARK: - Notification Config
|
|
102
|
+
|
|
103
|
+
Function("setNotificationConfig") { (config: [String: Any]) in
|
|
104
|
+
if let data = try? JSONSerialization.data(withJSONObject: config),
|
|
105
|
+
let json = String(data: data, encoding: .utf8) {
|
|
106
|
+
UserDefaults.standard.set(json, forKey: NOTIFICATION_CONFIG_KEY)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
// MARK: - Monitoring
|
|
101
111
|
|
|
102
|
-
AsyncFunction("startMonitoring") { (
|
|
112
|
+
AsyncFunction("startMonitoring") { (options: Any?, promise: Promise) in
|
|
113
|
+
var maxDistance: Double? = nil
|
|
114
|
+
if let dist = options as? Double {
|
|
115
|
+
maxDistance = dist
|
|
116
|
+
} else if let map = options as? [String: Any] {
|
|
117
|
+
maxDistance = map["maxDistance"] as? Double
|
|
118
|
+
if let notifications = map["notifications"] as? [String: Any],
|
|
119
|
+
let data = try? JSONSerialization.data(withJSONObject: notifications),
|
|
120
|
+
let json = String(data: data, encoding: .utf8) {
|
|
121
|
+
UserDefaults.standard.set(json, forKey: NOTIFICATION_CONFIG_KEY)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
103
124
|
if let dist = maxDistance {
|
|
104
125
|
UserDefaults.standard.set(dist, forKey: MAX_DISTANCE_KEY)
|
|
105
126
|
} else {
|
|
@@ -274,10 +295,32 @@ public class ExpoBeaconModule: Module, CLLocationManagerDelegate {
|
|
|
274
295
|
}
|
|
275
296
|
|
|
276
297
|
private func postBeaconNotification(identifier: String, eventType: String) {
|
|
298
|
+
let cfg = loadNotificationConfig()
|
|
299
|
+
let eventsCfg = cfg["beaconEvents"] as? [String: Any]
|
|
300
|
+
|
|
301
|
+
// Respect the enabled flag (defaults to true)
|
|
302
|
+
if let enabled = eventsCfg?["enabled"] as? Bool, !enabled { return }
|
|
303
|
+
|
|
304
|
+
let defaultTitle = eventType == "enter" ? "Beacon Entered" : "Beacon Exited"
|
|
305
|
+
let title: String
|
|
306
|
+
if eventType == "enter" {
|
|
307
|
+
title = (eventsCfg?["enterTitle"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? defaultTitle
|
|
308
|
+
} else {
|
|
309
|
+
title = (eventsCfg?["exitTitle"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? defaultTitle
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let bodyTemplate = (eventsCfg?["body"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
313
|
+
?? "{identifier} region {event}ed"
|
|
314
|
+
let body = bodyTemplate
|
|
315
|
+
.replacingOccurrences(of: "{identifier}", with: identifier)
|
|
316
|
+
.replacingOccurrences(of: "{event}", with: eventType)
|
|
317
|
+
|
|
277
318
|
let content = UNMutableNotificationContent()
|
|
278
|
-
content.title =
|
|
279
|
-
content.body =
|
|
280
|
-
|
|
319
|
+
content.title = title
|
|
320
|
+
content.body = body
|
|
321
|
+
|
|
322
|
+
let playSound = eventsCfg?["sound"] as? Bool ?? true
|
|
323
|
+
if playSound { content.sound = .default }
|
|
281
324
|
|
|
282
325
|
let request = UNNotificationRequest(
|
|
283
326
|
identifier: "beacon_\(eventType)_\(identifier)_\(Date().timeIntervalSince1970)",
|
|
@@ -287,6 +330,14 @@ public class ExpoBeaconModule: Module, CLLocationManagerDelegate {
|
|
|
287
330
|
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
|
|
288
331
|
}
|
|
289
332
|
|
|
333
|
+
private func loadNotificationConfig() -> [String: Any] {
|
|
334
|
+
guard let json = UserDefaults.standard.string(forKey: NOTIFICATION_CONFIG_KEY),
|
|
335
|
+
let data = json.data(using: .utf8),
|
|
336
|
+
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
337
|
+
else { return [:] }
|
|
338
|
+
return dict
|
|
339
|
+
}
|
|
340
|
+
|
|
290
341
|
private func constraintMatches(_ a: CLBeaconIdentityConstraint, _ b: CLBeaconIdentityConstraint) -> Bool {
|
|
291
342
|
return a.uuid == b.uuid && a.major == b.major && a.minor == b.minor
|
|
292
343
|
}
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -46,6 +46,69 @@ export type BeaconDistanceEvent = {
|
|
|
46
46
|
distance: number;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
/** Configuration for beacon enter/exit event notifications. */
|
|
50
|
+
export type BeaconNotificationConfig = {
|
|
51
|
+
/** Whether to show enter/exit notifications. Default: true. */
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
/** Notification title on beacon enter. Default: "Beacon Entered". */
|
|
54
|
+
enterTitle?: string;
|
|
55
|
+
/** Notification title on beacon exit. Default: "Beacon Exited". */
|
|
56
|
+
exitTitle?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Notification body template. Supports {identifier} and {event} placeholders.
|
|
59
|
+
* Default: "{identifier} region {event}ed".
|
|
60
|
+
*/
|
|
61
|
+
body?: string;
|
|
62
|
+
/** Play a sound with the notification (iOS only). Default: true. */
|
|
63
|
+
sound?: boolean;
|
|
64
|
+
/** Android drawable resource name for the notification icon (e.g. "ic_notification"). */
|
|
65
|
+
icon?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Configuration for the Android foreground service notification (persistent status bar entry). */
|
|
69
|
+
export type ForegroundServiceConfig = {
|
|
70
|
+
/** Title of the persistent notification. Default: "Beacon Monitoring Active". */
|
|
71
|
+
title?: string;
|
|
72
|
+
/** Body text of the persistent notification. Default: "Monitoring for iBeacons in the background". */
|
|
73
|
+
text?: string;
|
|
74
|
+
/** Android drawable resource name for the notification icon. */
|
|
75
|
+
icon?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Configuration for the Android notification channel. */
|
|
79
|
+
export type NotificationChannelConfig = {
|
|
80
|
+
/** Channel display name shown in system settings. Default: "Beacon Monitoring". */
|
|
81
|
+
name?: string;
|
|
82
|
+
/** Channel description shown in system settings. Default: "Used for background iBeacon region monitoring". */
|
|
83
|
+
description?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Channel importance level. Default: 'low'.
|
|
86
|
+
* Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.
|
|
87
|
+
*/
|
|
88
|
+
importance?: "low" | "default" | "high";
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Combined notification configuration for all notification types. */
|
|
92
|
+
export type NotificationConfig = {
|
|
93
|
+
/** Settings for beacon enter/exit event notifications. */
|
|
94
|
+
beaconEvents?: BeaconNotificationConfig;
|
|
95
|
+
/** Settings for the persistent foreground service notification (Android only). */
|
|
96
|
+
foregroundService?: ForegroundServiceConfig;
|
|
97
|
+
/** Settings for the Android notification channel (Android only). */
|
|
98
|
+
channel?: NotificationChannelConfig;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Options accepted by startMonitoring(). */
|
|
102
|
+
export type MonitoringOptions = {
|
|
103
|
+
/**
|
|
104
|
+
* Maximum distance in metres for distance-based enter events.
|
|
105
|
+
* Exit events are always emitted when the region is lost.
|
|
106
|
+
*/
|
|
107
|
+
maxDistance?: number;
|
|
108
|
+
/** Notification configuration overrides to apply for this monitoring session. */
|
|
109
|
+
notifications?: NotificationConfig;
|
|
110
|
+
};
|
|
111
|
+
|
|
49
112
|
/** Module event map. */
|
|
50
113
|
export type ExpoBeaconModuleEvents = {
|
|
51
114
|
onBeaconEnter: (params: BeaconRegionEvent) => void;
|
package/src/ExpoBeaconModule.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
ExpoBeaconModuleEvents,
|
|
5
5
|
BeaconScanResult,
|
|
6
6
|
PairedBeacon,
|
|
7
|
+
NotificationConfig,
|
|
8
|
+
MonitoringOptions,
|
|
7
9
|
} from "./ExpoBeacon.types";
|
|
8
10
|
|
|
9
11
|
declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
@@ -33,15 +35,21 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
33
35
|
*/
|
|
34
36
|
getPairedBeacons(): PairedBeacon[];
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Set persistent notification configuration. Settings are saved and applied to all
|
|
40
|
+
* subsequent monitoring sessions until explicitly changed.
|
|
41
|
+
*/
|
|
42
|
+
setNotificationConfig(config: NotificationConfig): void;
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Start background region monitoring for all paired beacons.
|
|
38
46
|
* On Android starts a foreground service.
|
|
39
47
|
* On iOS starts CLLocationManager region monitoring.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* Accepts a plain number (backward-compatible maxDistance shorthand) or a
|
|
50
|
+
* MonitoringOptions object with maxDistance and/or notification overrides.
|
|
43
51
|
*/
|
|
44
|
-
startMonitoring(
|
|
52
|
+
startMonitoring(options?: MonitoringOptions | number): Promise<void>;
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Stop background region monitoring.
|
|
@@ -61,4 +69,14 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
61
69
|
requestPermissionsAsync(): Promise<boolean>;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
try {
|
|
73
|
+
// eslint-disable-next-line import/no-mutable-exports
|
|
74
|
+
var module = requireNativeModule<ExpoBeaconModule>("ExpoBeacon");
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"expo-beacon: native module not found. Make sure you are using a development build " +
|
|
78
|
+
"(not Expo Go) and have run `npx expo prebuild` followed by a native rebuild.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default module;
|
package/src/index.ts
CHANGED
|
@@ -7,5 +7,11 @@ export type {
|
|
|
7
7
|
PairedBeacon,
|
|
8
8
|
BeaconRegionEvent,
|
|
9
9
|
BeaconRangingEvent,
|
|
10
|
+
BeaconDistanceEvent,
|
|
10
11
|
ExpoBeaconModuleEvents,
|
|
12
|
+
NotificationConfig,
|
|
13
|
+
MonitoringOptions,
|
|
14
|
+
BeaconNotificationConfig,
|
|
15
|
+
ForegroundServiceConfig,
|
|
16
|
+
NotificationChannelConfig,
|
|
11
17
|
} from "./ExpoBeacon.types";
|