@wayq/beekon-rn 0.0.9 → 0.1.2

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.
Files changed (53) hide show
  1. package/BeekonRn.podspec +2 -2
  2. package/CHANGELOG.md +70 -7
  3. package/LICENSE.txt +3 -3
  4. package/README.md +111 -326
  5. package/android/build.gradle +2 -2
  6. package/android/src/main/AndroidManifest.xml +10 -0
  7. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +132 -1
  8. package/ios/BeekonRn.mm +5 -0
  9. package/ios/BeekonRn.swift +96 -10
  10. package/ios/Frameworks/BeekonKit.xcframework/LICENSE.txt +3 -3
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +6218 -3034
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +89 -5
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +6218 -3034
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +89 -5
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +6218 -3034
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  23. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +89 -5
  24. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
  25. package/lib/module/NativeBeekonRn.js +14 -0
  26. package/lib/module/NativeBeekonRn.js.map +1 -1
  27. package/lib/module/beekon.js +26 -1
  28. package/lib/module/beekon.js.map +1 -1
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/internal/mappers.js +95 -2
  31. package/lib/module/internal/mappers.js.map +1 -1
  32. package/lib/module/types/permission.js +2 -0
  33. package/lib/module/types/permission.js.map +1 -0
  34. package/lib/typescript/src/NativeBeekonRn.d.ts +39 -0
  35. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  36. package/lib/typescript/src/beekon.d.ts +20 -0
  37. package/lib/typescript/src/beekon.d.ts.map +1 -1
  38. package/lib/typescript/src/index.d.ts +2 -1
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/internal/mappers.d.ts +4 -1
  41. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  42. package/lib/typescript/src/types/geofence.d.ts +37 -0
  43. package/lib/typescript/src/types/geofence.d.ts.map +1 -1
  44. package/lib/typescript/src/types/permission.d.ts +78 -0
  45. package/lib/typescript/src/types/permission.d.ts.map +1 -0
  46. package/package.json +5 -5
  47. package/scripts/fetch-beekonkit.sh +6 -6
  48. package/src/NativeBeekonRn.ts +45 -0
  49. package/src/beekon.ts +33 -0
  50. package/src/index.tsx +12 -0
  51. package/src/internal/mappers.ts +140 -0
  52. package/src/types/geofence.ts +41 -0
  53. package/src/types/permission.ts +91 -0
@@ -1,7 +1,7 @@
1
1
  buildscript {
2
2
  ext.BeekonRn = [
3
3
  kotlinVersion: "2.1.20",
4
- // minSdk 26 is required by the native Beekon AAR (io.github.wayqteam:beekon).
4
+ // minSdk 26 is required by the native Beekon AAR (io.github.beekonlabs:beekon).
5
5
  minSdkVersion: 26,
6
6
  compileSdkVersion: 36,
7
7
  targetSdkVersion: 36
@@ -79,7 +79,7 @@ dependencies {
79
79
  // the SDK reaches v1 stability. 0.0.7 is the release that ships the license
80
80
  // API (setWrapperInfo / BeekonConfig.licenseKey / Beekon.licenseStatus);
81
81
  // 0.0.8 embeds the production ES256 verification keyset.
82
- implementation "io.github.wayqteam:beekon:0.0.9"
82
+ implementation "io.github.beekonlabs:beekon:0.1.2"
83
83
  // Kotlin coroutines — required for collecting Beekon's StateFlow/SharedFlow.
84
84
  // Beekon already depends on coroutines transitively, but declaring it here
85
85
  // makes the dependency intent explicit.
@@ -1,2 +1,12 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!--
3
+ Declares the build-time license-gate product id (build-gate-v1 §6): a static
4
+ <meta-data> that manifest-merges into the host so the gate validates against
5
+ the `rn` product, mirroring the runtime setWrapperInfo("rn", …). No runtime effect.
6
+ -->
7
+ <application>
8
+ <meta-data
9
+ android:name="in.wayq.beekon.product"
10
+ android:value="rn" />
11
+ </application>
2
12
  </manifest>
@@ -18,8 +18,10 @@ import `in`.wayq.beekon.Beekon
18
18
  import `in`.wayq.beekon.BeekonConfig
19
19
  import `in`.wayq.beekon.BeekonException
20
20
  import `in`.wayq.beekon.BeekonGeofence
21
+ import `in`.wayq.beekon.BeekonPermission
21
22
  import `in`.wayq.beekon.BeekonState
22
23
  import `in`.wayq.beekon.GeofenceEvent
24
+ import `in`.wayq.beekon.GeofenceNotification
23
25
  import `in`.wayq.beekon.LicenseStatus
24
26
  import `in`.wayq.beekon.Location
25
27
  import `in`.wayq.beekon.LocationQuality
@@ -29,6 +31,12 @@ import `in`.wayq.beekon.LogEntry
29
31
  import `in`.wayq.beekon.LogLevel
30
32
  import `in`.wayq.beekon.MotionState
31
33
  import `in`.wayq.beekon.NotificationConfig
34
+ import `in`.wayq.beekon.NotificationContent
35
+ import `in`.wayq.beekon.NotificationDelivery
36
+ import `in`.wayq.beekon.NotificationImportance
37
+ import `in`.wayq.beekon.PermissionImportance
38
+ import `in`.wayq.beekon.PermissionRequirement
39
+ import `in`.wayq.beekon.PermissionStatus
32
40
  import `in`.wayq.beekon.StationaryMode
33
41
  import `in`.wayq.beekon.StopReason
34
42
  import `in`.wayq.beekon.SyncConfig
@@ -37,6 +45,7 @@ import `in`.wayq.beekon.SyncStatus
37
45
  import `in`.wayq.beekon.Transition
38
46
  import java.time.Instant
39
47
  import kotlinx.coroutines.CoroutineScope
48
+ import org.json.JSONObject
40
49
  import kotlinx.coroutines.Dispatchers
41
50
  import kotlinx.coroutines.SupervisorJob
42
51
  import kotlinx.coroutines.cancel
@@ -235,6 +244,18 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
235
244
  promise.resolve(licenseStatusToWire(Beekon.licenseStatus.value))
236
245
  }
237
246
 
247
+ override fun getPermissionStatus(promise: Promise) {
248
+ // Synchronous, non-throwing read on the native SDK.
249
+ promise.resolve(permissionStatusToWire(Beekon.getPermissionStatus()))
250
+ }
251
+
252
+ override fun getRequiredPermissions(promise: Promise) {
253
+ // Synchronous, non-throwing read on the native SDK.
254
+ val arr: WritableArray = Arguments.createArray()
255
+ for (r in Beekon.getRequiredPermissions()) arr.pushMap(permissionRequirementToWire(r))
256
+ promise.resolve(arr)
257
+ }
258
+
238
259
  // ---------------------------------------------------------------------------
239
260
  // Diagnostic logs (spec diagnostics/log-format-v1)
240
261
  // ---------------------------------------------------------------------------
@@ -440,6 +461,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
440
461
  radiusMeters = m.getDouble("radiusMeters"),
441
462
  notifyOnEntry = m.getBoolean("notifyOnEntry"),
442
463
  notifyOnExit = m.getBoolean("notifyOnExit"),
464
+ notification = GeofenceNotificationWire.decode(m.getString("notificationJson")),
443
465
  )
444
466
  }
445
467
  return out
@@ -488,6 +510,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
488
510
  m.putDouble("radiusMeters", g.radiusMeters)
489
511
  m.putBoolean("notifyOnEntry", g.notifyOnEntry)
490
512
  m.putBoolean("notifyOnExit", g.notifyOnExit)
513
+ g.notification?.let { m.putString("notificationJson", GeofenceNotificationWire.encode(it)) }
491
514
  return m
492
515
  }
493
516
 
@@ -513,6 +536,48 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
513
536
  return m
514
537
  }
515
538
 
539
+ private fun permissionStatusToWire(p: PermissionStatus): WritableMap {
540
+ val m = Arguments.createMap()
541
+ m.putString("level", permissionLevelToWire(p.level))
542
+ // '' is the wire encoding of null (not granted).
543
+ m.putString("accuracy", p.accuracy?.let { permissionAccuracyToWire(it) } ?: "")
544
+ return m
545
+ }
546
+
547
+ private fun permissionLevelToWire(l: PermissionStatus.Level): String = when (l) {
548
+ PermissionStatus.Level.NotDetermined -> "notDetermined"
549
+ PermissionStatus.Level.Denied -> "denied"
550
+ PermissionStatus.Level.Restricted -> "restricted"
551
+ PermissionStatus.Level.Foreground -> "foreground"
552
+ PermissionStatus.Level.Background -> "background"
553
+ }
554
+
555
+ private fun permissionAccuracyToWire(a: PermissionStatus.Accuracy): String = when (a) {
556
+ PermissionStatus.Accuracy.Full -> "full"
557
+ PermissionStatus.Accuracy.Reduced -> "reduced"
558
+ }
559
+
560
+ private fun permissionRequirementToWire(r: PermissionRequirement): WritableMap {
561
+ val m = Arguments.createMap()
562
+ m.putString("permission", permissionKindToWire(r.permission))
563
+ m.putString("importance", permissionImportanceToWire(r.importance))
564
+ m.putBoolean("satisfied", r.satisfied)
565
+ m.putString("rationale", r.rationale)
566
+ return m
567
+ }
568
+
569
+ private fun permissionKindToWire(p: BeekonPermission): String = when (p) {
570
+ BeekonPermission.Location -> "location"
571
+ BeekonPermission.BackgroundLocation -> "backgroundLocation"
572
+ BeekonPermission.ActivityRecognition -> "activityRecognition"
573
+ BeekonPermission.Notifications -> "notifications"
574
+ }
575
+
576
+ private fun permissionImportanceToWire(i: PermissionImportance): String = when (i) {
577
+ PermissionImportance.Required -> "required"
578
+ PermissionImportance.Recommended -> "recommended"
579
+ }
580
+
516
581
  private fun syncStatusToWire(s: SyncStatus): WritableMap {
517
582
  val m = Arguments.createMap()
518
583
  when (s) {
@@ -678,6 +743,72 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
678
743
  // Reported to the native verifier via setWrapperInfo (diagnostics only — the
679
744
  // verifier consumes only the product). Keep in sync with package.json
680
745
  // "version" on release.
681
- private const val WRAPPER_VERSION = "0.0.9"
746
+ private const val WRAPPER_VERSION = "0.1.2"
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Wrapper-local (de)serialization for [GeofenceNotification] in the canonical JSON shape shared
752
+ * with the native SDKs. The SDK's own `GeofenceNotificationJson` is `internal`, so the module
753
+ * cannot reuse it; this mirrors it exactly. Tolerant: unknown enums fall back to defaults and a
754
+ * malformed/empty payload yields null.
755
+ */
756
+ private object GeofenceNotificationWire {
757
+ fun encode(n: GeofenceNotification): String {
758
+ val obj = JSONObject()
759
+ obj.put("delivery", if (n.delivery == NotificationDelivery.Cloud) "cloud" else "local")
760
+ n.onEnter?.let { obj.put("onEnter", contentToJson(it)) }
761
+ n.onExit?.let { obj.put("onExit", contentToJson(it)) }
762
+ return obj.toString()
763
+ }
764
+
765
+ fun decode(json: String?): GeofenceNotification? {
766
+ if (json.isNullOrBlank()) return null
767
+ return runCatching {
768
+ val obj = JSONObject(json)
769
+ val onEnter = contentFromJson(obj.optJSONObject("onEnter"))
770
+ val onExit = contentFromJson(obj.optJSONObject("onExit"))
771
+ if (onEnter == null && onExit == null) {
772
+ null
773
+ } else {
774
+ val delivery = if (obj.optString("delivery", "local") == "cloud") {
775
+ NotificationDelivery.Cloud
776
+ } else {
777
+ NotificationDelivery.Local
778
+ }
779
+ GeofenceNotification(onEnter = onEnter, onExit = onExit, delivery = delivery)
780
+ }
781
+ }.getOrNull()
782
+ }
783
+
784
+ private fun contentToJson(c: NotificationContent): JSONObject {
785
+ val obj = JSONObject()
786
+ obj.put("title", c.title)
787
+ obj.put("body", c.body)
788
+ obj.put("importance", if (c.importance == NotificationImportance.Default) "default" else "high")
789
+ c.deepLink?.let { obj.put("deepLink", it) }
790
+ if (c.data.isNotEmpty()) {
791
+ val data = JSONObject()
792
+ for ((k, v) in c.data) data.put(k, v)
793
+ obj.put("data", data)
794
+ }
795
+ return obj
796
+ }
797
+
798
+ private fun contentFromJson(obj: JSONObject?): NotificationContent? {
799
+ if (obj == null) return null
800
+ val title = obj.optString("title", "")
801
+ val body = obj.optString("body", "")
802
+ if (title.isEmpty() || body.isEmpty()) return null
803
+ val importance = if (obj.optString("importance", "high") == "default") {
804
+ NotificationImportance.Default
805
+ } else {
806
+ NotificationImportance.High
807
+ }
808
+ val deepLink = if (obj.has("deepLink") && !obj.isNull("deepLink")) obj.optString("deepLink") else null
809
+ val data = obj.optJSONObject("data")?.let { d ->
810
+ buildMap { for (key in d.keys()) put(key, d.optString(key)) }
811
+ } ?: emptyMap()
812
+ return NotificationContent(title = title, body = body, importance = importance, deepLink = deepLink, data = data)
682
813
  }
683
814
  }
package/ios/BeekonRn.mm CHANGED
@@ -150,6 +150,11 @@
150
150
  [_impl licenseStatusWithResolver:resolve rejecter:reject];
151
151
  }
152
152
 
153
+ - (void)getPermissionStatus:(RCTPromiseResolveBlock)resolve
154
+ reject:(RCTPromiseRejectBlock)reject {
155
+ [_impl getPermissionStatusWithResolver:resolve rejecter:reject];
156
+ }
157
+
153
158
  // MARK: - Diagnostic logs
154
159
 
155
160
  - (void)getLog:(double)fromMs
@@ -37,7 +37,7 @@ import BeekonKit
37
37
  // Reported to the native verifier via setWrapperInfo (diagnostics only — the
38
38
  // verifier consumes only the product). Keep in sync with package.json
39
39
  // "version" on release.
40
- private static let wrapperVersion = "0.0.9"
40
+ private static let wrapperVersion = "0.1.2"
41
41
 
42
42
  @objc public init(
43
43
  onState: @escaping (NSDictionary) -> Void,
@@ -345,6 +345,26 @@ import BeekonKit
345
345
  }
346
346
  }
347
347
 
348
+ @objc public func getPermissionStatusWithResolver(
349
+ _ resolve: @escaping @Sendable (Any?) -> Void,
350
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
351
+ ) {
352
+ // `getPermissionStatus()` is `nonisolated` + synchronous — no Task/await.
353
+ resolve(permissionStatusToWire(Beekon.shared.getPermissionStatus()))
354
+ }
355
+
356
+ @objc public func getRequiredPermissionsWithResolver(
357
+ _ resolve: @escaping @Sendable (Any?) -> Void,
358
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
359
+ ) {
360
+ // `getRequiredPermissions()` is actor-isolated (`await`).
361
+ Task { [weak self] in
362
+ guard let self = self else { return }
363
+ let requirements = await Beekon.shared.getRequiredPermissions()
364
+ resolve(requirements.map { self.permissionRequirementToWire($0) })
365
+ }
366
+ }
367
+
348
368
  // MARK: - Diagnostic logs (spec diagnostics/log-format-v1)
349
369
 
350
370
  @objc public func getLogFromMs(
@@ -496,7 +516,12 @@ import BeekonKit
496
516
  longitude: lng,
497
517
  radiusMeters: radius,
498
518
  notifyOnEntry: (m["notifyOnEntry"] as? NSNumber)?.boolValue ?? true,
499
- notifyOnExit: (m["notifyOnExit"] as? NSNumber)?.boolValue ?? true
519
+ notifyOnExit: (m["notifyOnExit"] as? NSNumber)?.boolValue ?? true,
520
+ // Native `GeofenceNotification` is Codable in the canonical shape; a malformed
521
+ // string degrades to nil rather than failing the add.
522
+ notification: (m["notificationJson"] as? String).flatMap {
523
+ try? JSONDecoder().decode(GeofenceNotification.self, from: Data($0.utf8))
524
+ }
500
525
  )
501
526
  )
502
527
  }
@@ -565,14 +590,19 @@ import BeekonKit
565
590
  }
566
591
 
567
592
  private func geofenceToWire(_ g: BeekonGeofence) -> NSDictionary {
568
- return [
569
- "id": g.id,
570
- "lat": g.latitude,
571
- "lng": g.longitude,
572
- "radiusMeters": g.radiusMeters,
573
- "notifyOnEntry": g.notifyOnEntry,
574
- "notifyOnExit": g.notifyOnExit,
575
- ]
593
+ let m = NSMutableDictionary()
594
+ m["id"] = g.id
595
+ m["lat"] = g.latitude
596
+ m["lng"] = g.longitude
597
+ m["radiusMeters"] = g.radiusMeters
598
+ m["notifyOnEntry"] = g.notifyOnEntry
599
+ m["notifyOnExit"] = g.notifyOnExit
600
+ if let notification = g.notification,
601
+ let data = try? JSONEncoder().encode(notification),
602
+ let json = String(data: data, encoding: .utf8) {
603
+ m["notificationJson"] = json
604
+ }
605
+ return m
576
606
  }
577
607
 
578
608
  private func geofenceEventToWire(_ e: GeofenceEvent) -> NSDictionary {
@@ -594,6 +624,62 @@ import BeekonKit
594
624
  ]
595
625
  }
596
626
 
627
+ private func permissionStatusToWire(_ p: PermissionStatus) -> NSDictionary {
628
+ return [
629
+ "level": permissionLevelToWire(p.level),
630
+ // "" is the wire encoding of null (not granted).
631
+ "accuracy": p.accuracy.map { permissionAccuracyToWire($0) } ?? "",
632
+ ]
633
+ }
634
+
635
+ private func permissionLevelToWire(_ l: PermissionStatus.Level) -> String {
636
+ switch l {
637
+ case .notDetermined: return "notDetermined"
638
+ case .denied: return "denied"
639
+ case .restricted: return "restricted"
640
+ case .foreground: return "foreground"
641
+ case .background: return "background"
642
+ // BeekonKit enums are non-frozen (library-evolution binary); handle unknowns.
643
+ @unknown default: return "notDetermined"
644
+ }
645
+ }
646
+
647
+ private func permissionAccuracyToWire(_ a: PermissionStatus.Accuracy) -> String {
648
+ switch a {
649
+ case .full: return "full"
650
+ case .reduced: return "reduced"
651
+ @unknown default: return "full"
652
+ }
653
+ }
654
+
655
+ private func permissionRequirementToWire(_ r: PermissionRequirement) -> NSDictionary {
656
+ return [
657
+ "permission": permissionKindToWire(r.permission),
658
+ "importance": permissionImportanceToWire(r.importance),
659
+ "satisfied": r.satisfied,
660
+ "rationale": r.rationale,
661
+ ]
662
+ }
663
+
664
+ private func permissionKindToWire(_ p: PermissionRequirement.Permission) -> String {
665
+ switch p {
666
+ case .location: return "location"
667
+ case .backgroundLocation: return "backgroundLocation"
668
+ case .activityRecognition: return "activityRecognition"
669
+ case .notifications: return "notifications"
670
+ // BeekonKit enums are non-frozen (library-evolution binary); handle unknowns.
671
+ @unknown default: return "location"
672
+ }
673
+ }
674
+
675
+ private func permissionImportanceToWire(_ i: PermissionRequirement.Importance) -> String {
676
+ switch i {
677
+ case .required: return "required"
678
+ case .recommended: return "recommended"
679
+ @unknown default: return "recommended"
680
+ }
681
+ }
682
+
597
683
  private func stateToWire(_ s: BeekonState) -> NSDictionary {
598
684
  switch s {
599
685
  case .idle:
@@ -1,9 +1,9 @@
1
1
  Beekon SDK
2
- Copyright (c) 2026 wayqteam. All rights reserved.
2
+ Copyright (c) 2026 beekonlabs. All rights reserved.
3
3
 
4
4
  This software is licensed for evaluation use only. No production deployment,
5
5
  redistribution, sublicensing, or commercial use is permitted without a separate
6
- written agreement with wayqteam.
6
+ written agreement with beekonlabs.
7
7
 
8
8
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9
9
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
@@ -11,4 +11,4 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
11
11
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
12
12
  FROM USE OF THE SOFTWARE.
13
13
 
14
- For licensing inquiries: contact wayqteam.
14
+ For licensing inquiries: contact beekonlabs.