spotny-sdk 0.5.5 → 0.6.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.

Potentially problematic release.


This version of spotny-sdk might be problematic. Click here for more details.

package/README.md CHANGED
@@ -65,12 +65,18 @@ import {
65
65
  export default function App() {
66
66
  useEffect(() => {
67
67
  // 1. Initialize once before anything else
68
- initialize({ source: 'nike', maxDetectionDistance: 10 });
68
+ initialize({
69
+ token: 'YOUR_SDK_TOKEN', // required — from your Spotny dashboard
70
+ source: 'nike',
71
+ maxDetectionDistance: 10,
72
+ });
69
73
 
70
74
  // 2. Listen for nearby beacons
71
75
  const beaconSub = addBeaconsRangedListener(({ beacons, region }) => {
72
76
  beacons.forEach((b) =>
73
- console.log(`${b.major}/${b.minor} — ${b.distance.toFixed(1)}m (${b.proximity})`)
77
+ console.log(
78
+ `${b.major}/${b.minor} — ${b.distance.toFixed(1)}m (${b.proximity})`
79
+ )
74
80
  );
75
81
  });
76
82
 
@@ -103,16 +109,18 @@ export default function App() {
103
109
 
104
110
  **Must be called before any other SDK function.** Configures the SDK with your brand settings.
105
111
 
106
- | Option | Type | Default | Description |
107
- | ------------------------- | -------- | ------- | -------------------------------------------------------------- |
108
- | `source` | `string` | | Your brand or app identifier (e.g. `'nike'`). Sent with every tracking event. |
109
- | `maxDetectionDistance` | `number` | `8.0` | Maximum detection radius in metres. Beacons beyond this are ignored. |
110
- | `distanceCorrectionFactor`| `number` | `0.5` | Multiplier applied to raw RSSI distance. Tune for your beacon TX power. |
112
+ | Option | Type | Default | Description |
113
+ | -------------------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
114
+ | `token` | `string` | **required** | SDK token issued by Spotny for your app. Verified against the backend — rejects with `UNAUTHORIZED` if invalid. |
115
+ | `source` | `string` | | Your brand or app identifier (e.g. `'nike'`). Sent with every tracking event. |
116
+ | `maxDetectionDistance` | `number` | `8.0` | Maximum detection radius in metres. Beacons beyond this are ignored. |
117
+ | `distanceCorrectionFactor` | `number` | `0.5` | Multiplier applied to raw RSSI distance. Tune for your beacon TX power. |
111
118
 
112
- Returns `Promise<string>`.
119
+ Returns `Promise<string>`. **Rejects** if the token is missing, invalid, or the network request fails — handle the error before calling `startScanner()`.
113
120
 
114
121
  ```ts
115
122
  await initialize({
123
+ token: 'YOUR_SDK_TOKEN',
116
124
  source: 'nike',
117
125
  maxDetectionDistance: 10,
118
126
  distanceCorrectionFactor: 0.5,
@@ -179,13 +187,13 @@ sub.remove(); // call on cleanup
179
187
 
180
188
  Each beacon in the array:
181
189
 
182
- | Field | Type | Description |
183
- | ----------- | -------- | --------------------------------------------------------------- |
184
- | `uuid` | `string` | Beacon proximity UUID |
185
- | `major` | `number` | Beacon major value |
186
- | `minor` | `number` | Beacon minor value |
187
- | `distance` | `number` | Estimated distance in metres (after correction factor applied) |
188
- | `rssi` | `number` | Raw signal strength in dBm |
190
+ | Field | Type | Description |
191
+ | ----------- | -------- | -------------------------------------------------------------- |
192
+ | `uuid` | `string` | Beacon proximity UUID |
193
+ | `major` | `number` | Beacon major value |
194
+ | `minor` | `number` | Beacon minor value |
195
+ | `distance` | `number` | Estimated distance in metres (after correction factor applied) |
196
+ | `rssi` | `number` | Raw signal strength in dBm |
189
197
  | `proximity` | `string` | `'immediate'` \| `'near'` \| `'far'` \| `'unknown'` |
190
198
 
191
199
  ---
@@ -196,8 +204,8 @@ Fires when the user enters or exits a beacon region. Works in foreground, backgr
196
204
 
197
205
  ```ts
198
206
  const sub = addBeaconRegionListener(({ event, region, state }) => {
199
- if (event === 'enter') console.log('Entered region', region);
200
- if (event === 'exit') console.log('Left region', region);
207
+ if (event === 'enter') console.log('Entered region', region);
208
+ if (event === 'exit') console.log('Left region', region);
201
209
  if (event === 'determined') console.log('State for', region, '→', state);
202
210
  });
203
211
 
@@ -49,6 +49,8 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
49
49
  private var distanceCorrectionFactor = 0.5
50
50
  /** Company/brand name identifying the SDK consumer (e.g. "nike"). */
51
51
  private var source: String? = null
52
+ /** Bearer token used to authorise all API calls. Set during initialize(). */
53
+ private var sdkToken: String? = null
52
54
 
53
55
  // ── Timing constants ──────────────────────────────────────────────────────
54
56
  private var campaignFetchCooldown = 5_000L // ms
@@ -139,8 +141,32 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
139
141
  config?.getDouble("distanceCorrectionFactor")
140
142
  .takeIf { config?.hasKey("distanceCorrectionFactor") == true && it > 0 }
141
143
  ?.let { distanceCorrectionFactor = it; Log.d(TAG, "distanceCorrectionFactor = $it") }
142
- Log.d(TAG, "SDK initialized")
143
- promise.resolve("SDK initialized")
144
+ val token = config?.getString("token")?.takeIf { it.isNotBlank() }
145
+ if (token == null) {
146
+ promise.reject("MISSING_TOKEN", "initialize() requires a token")
147
+ return
148
+ }
149
+ sdkToken = token
150
+
151
+ // Verify the token — device_id and source are sent as headers automatically
152
+ post("/api/app/sdk/verify", emptyMap()) { status, _ ->
153
+ when {
154
+ status in 200..299 -> {
155
+ Log.d(TAG, "SDK initialized and authorized (source: $source)")
156
+ promise.resolve("SDK initialized")
157
+ }
158
+ status == 401 || status == 403 -> {
159
+ sdkToken = null
160
+ Log.w(TAG, "Token unauthorized ($status)")
161
+ promise.reject("UNAUTHORIZED", "Invalid or expired SDK token")
162
+ }
163
+ else -> {
164
+ sdkToken = null
165
+ Log.w(TAG, "Verification failed ($status)")
166
+ promise.reject("VERIFY_FAILED", "SDK verification returned status $status")
167
+ }
168
+ }
169
+ }
144
170
  }
145
171
 
146
172
  override fun requestNotificationPermissions(promise: Promise) {
@@ -173,9 +199,7 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
173
199
 
174
200
  override fun identify(userId: String, promise: Promise) {
175
201
  identifiedUserId = userId
176
- val payload = mutableMapOf<String, Any?>("device_id" to getDeviceId(), "user_id" to userId)
177
- source?.let { payload["source"] = it }
178
- post("/api/app/users/identify", payload) { status, _ ->
202
+ post("/api/app/users/identify", mapOf("user_id" to userId)) { status, _ ->
179
203
  if (status in 200..299) {
180
204
  Log.d(TAG, "Identified user $userId")
181
205
  promise.resolve("User identified")
@@ -220,7 +244,6 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
220
244
  // ── Range notifier ────────────────────────────────────────────────────────
221
245
 
222
246
  private val rangeNotifier = RangeNotifier { beacons, region ->
223
- val deviceId = getDeviceId()
224
247
  val now = System.currentTimeMillis()
225
248
 
226
249
  // JS event payload
@@ -260,13 +283,13 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
260
283
  if (activeCampaigns.containsKey(key)) {
261
284
  val isFirst = !lastProximityEventSent.containsKey(key)
262
285
  if (isFirst) {
263
- sendProximity("NEARBY", key, distance, deviceId)
286
+ sendProximity("NEARBY", key, distance)
264
287
  lastProximityDistance[key] = distance
265
288
  lastProximityEventSent[key] = now
266
289
  } else if (distance >= 1.0) {
267
290
  val lastDist = lastProximityDistance[key] ?: 0.0
268
291
  if (abs(distance - lastDist) >= proximityDistanceThreshold) {
269
- sendProximity("NEARBY", key, distance, deviceId)
292
+ sendProximity("NEARBY", key, distance)
270
293
  lastProximityDistance[key] = distance
271
294
  lastProximityEventSent[key] = now
272
295
  }
@@ -275,12 +298,12 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
275
298
  if (campaign?.campaignId != null && campaign.inQueue == false && distance <= impressionDistance) {
276
299
  val lastImp = lastImpressionEventSent[key]
277
300
  if (lastImp == null || now - lastImp >= impressionEventInterval) {
278
- sendImpression(key, distance, deviceId)
301
+ sendImpression(key, distance)
279
302
  lastImpressionEventSent[key] = now
280
303
  }
281
304
  }
282
305
  } else {
283
- fetchCampaign(major, minor, deviceId)
306
+ fetchCampaign(major, minor)
284
307
  }
285
308
  }
286
309
  }
@@ -305,9 +328,8 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
305
328
  putString("event", "exit")
306
329
  }
307
330
  sendEvent("onBeaconRegionEvent", payload)
308
- val deviceId = getDeviceId()
309
331
  for (key in activeCampaigns.keys.toList()) {
310
- cleanupBeacon(key, deviceId)
332
+ cleanupBeacon(key)
311
333
  }
312
334
  try { beaconManager.stopRangingBeaconsInRegion(region) } catch (_: RemoteException) {}
313
335
  }
@@ -362,8 +384,8 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
362
384
 
363
385
  // ── State cleanup ─────────────────────────────────────────────────────────
364
386
 
365
- private fun cleanupBeacon(key: String, deviceId: String) {
366
- sendProximity("PROXIMITY_EXIT", key, 0.0, deviceId)
387
+ private fun cleanupBeacon(key: String) {
388
+ sendProximity("PROXIMITY_EXIT", key, 0.0)
367
389
  activeCampaigns.remove(key)
368
390
  lastProximityEventSent.remove(key)
369
391
  lastProximityDistance.remove(key)
@@ -376,12 +398,11 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
376
398
  }
377
399
 
378
400
  private fun cleanupAllState() {
379
- val deviceId = getDeviceId()
380
401
  // Stagger exit events to avoid firing N simultaneous network requests
381
402
  val keys = activeCampaigns.keys.toList()
382
403
  keys.forEachIndexed { index, key ->
383
404
  android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
384
- cleanupBeacon(key, deviceId)
405
+ cleanupBeacon(key)
385
406
  }, index * 300L)
386
407
  }
387
408
  }
@@ -398,6 +419,9 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
398
419
  val conn = (URL("$backendURL$endpoint").openConnection() as HttpURLConnection).apply {
399
420
  requestMethod = "POST"
400
421
  setRequestProperty("Content-Type", "application/json")
422
+ setRequestProperty("X-Device-ID", getDeviceId())
423
+ sdkToken?.let { setRequestProperty("Authorization", "Bearer $it") }
424
+ source?.let { setRequestProperty("X-Source", it) }
401
425
  connectTimeout = 10_000
402
426
  readTimeout = 10_000
403
427
  doOutput = true
@@ -436,7 +460,7 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
436
460
 
437
461
  // ── Campaign fetching ─────────────────────────────────────────────────────
438
462
 
439
- private fun fetchCampaign(major: Int, minor: Int, deviceId: String) {
463
+ private fun fetchCampaign(major: Int, minor: Int) {
440
464
  val key = beaconKey(major, minor)
441
465
  if (activeCampaigns.containsKey(key)) return
442
466
  if (fetchInProgress[key] == true) return
@@ -447,9 +471,8 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
447
471
  lastCampaignFetchAttempt[key] = System.currentTimeMillis()
448
472
  fetchInProgress[key] = true
449
473
 
450
- val payload = mutableMapOf<String, Any?>("beacon_id" to key, "device_id" to deviceId)
474
+ val payload = mutableMapOf<String, Any?>("beacon_id" to key)
451
475
  identifiedUserId?.let { payload["user_id"] = it }
452
- source?.let { payload["source"] = it }
453
476
 
454
477
  post("/api/app/campaigns/beacon", payload) { status, body ->
455
478
  fetchInProgress[key] = false
@@ -487,7 +510,6 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
487
510
  eventType: String,
488
511
  key: String,
489
512
  distance: Double,
490
- deviceId: String,
491
513
  endpoint: String
492
514
  ) {
493
515
  val isImpression = eventType == "IMPRESSION_HEARTBEAT"
@@ -509,14 +531,12 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
509
531
 
510
532
  val payload = mutableMapOf<String, Any?>(
511
533
  "event_type" to eventType,
512
- "device_id" to deviceId,
513
534
  "distance" to distance,
514
535
  "screen_id" to campaign.screenId
515
536
  )
516
537
  campaign.campaignId?.let { payload["campaign_id"] = it }
517
538
  campaign.sessionId?.let { payload["session_id"] = it }
518
539
  identifiedUserId?.let { payload["user_id"] = it }
519
- source?.let { payload["source"] = it }
520
540
 
521
541
  post(endpoint, payload) { status, body ->
522
542
  if (status in 200..299) {
@@ -546,11 +566,11 @@ class SpotnySdkModule(private val reactContext: ReactApplicationContext) :
546
566
  }
547
567
  }
548
568
 
549
- private fun sendProximity(eventType: String, key: String, distance: Double, deviceId: String) =
550
- sendTracking(eventType, key, distance, deviceId, "/api/app/impressions/proximity")
569
+ private fun sendProximity(eventType: String, key: String, distance: Double) =
570
+ sendTracking(eventType, key, distance, "/api/app/impressions/proximity")
551
571
 
552
- private fun sendImpression(key: String, distance: Double, deviceId: String) =
553
- sendTracking("IMPRESSION_HEARTBEAT", key, distance, deviceId, "/api/app/impressions/track")
572
+ private fun sendImpression(key: String, distance: Double) =
573
+ sendTracking("IMPRESSION_HEARTBEAT", key, distance, "/api/app/impressions/track")
554
574
 
555
575
  // ── Minimal JSON parser (avoids org.json / GSON dependency) ──────────────
556
576
  // Only handles flat and one-level-deep objects. For this SDK, that is enough.
@@ -61,6 +61,8 @@ public class SpotnyBeaconScanner: NSObject {
61
61
  private var distanceCorrectionFactor: Double = 0.5
62
62
  /// Company/brand name that identifies the SDK consumer (e.g. "nike").
63
63
  private var source: String?
64
+ /// Bearer token used to authorise all API calls. Set during initialize().
65
+ private var sdkToken: String?
64
66
 
65
67
  // ── Session TTL ────────────────────────────────────────────────────────────
66
68
  /// Sessions older than this when the app resumes are considered stale (crash / force-quit).
@@ -196,6 +198,7 @@ public class SpotnyBeaconScanner: NSObject {
196
198
  resolve: @escaping (Any?) -> Void,
197
199
  reject: @escaping (String?, String?, Error?) -> Void
198
200
  ) {
201
+ // Apply config values
199
202
  if let dist = config["maxDetectionDistance"] as? Double {
200
203
  maxDetectionDistance = dist
201
204
  print("⚙️ SpotnySDK: maxDetectionDistance = \(dist)m")
@@ -208,8 +211,35 @@ public class SpotnyBeaconScanner: NSObject {
208
211
  distanceCorrectionFactor = factor
209
212
  print("⚙️ SpotnySDK: distanceCorrectionFactor = \(factor)")
210
213
  }
211
- print(" SpotnySDK: Initialized")
212
- resolve("SDK initialized")
214
+ guard let token = config["token"] as? String, !token.isEmpty else {
215
+ reject("MISSING_TOKEN", "initialize() requires a token", nil)
216
+ return
217
+ }
218
+ sdkToken = token
219
+
220
+ // Verify the token — device_id and source are sent as headers automatically
221
+ post(endpoint: "/api/app/sdk/verify", payload: [:]) { [weak self] result in
222
+ guard let self = self else { return }
223
+ switch result {
224
+ case .success(let (status, _)):
225
+ if 200...299 ~= status {
226
+ print("✅ SpotnySDK: Initialized and authorized (source: \(self.source ?? "none"))")
227
+ resolve("SDK initialized")
228
+ } else if status == 401 || status == 403 {
229
+ self.sdkToken = nil
230
+ print("❌ SpotnySDK: Token unauthorized (\(status))")
231
+ reject("UNAUTHORIZED", "Invalid or expired SDK token", nil)
232
+ } else {
233
+ self.sdkToken = nil
234
+ print("❌ SpotnySDK: Verification failed (\(status))")
235
+ reject("VERIFY_FAILED", "SDK verification returned status \(status)", nil)
236
+ }
237
+ case .failure(let error):
238
+ self.sdkToken = nil
239
+ print("❌ SpotnySDK: Verification network error — \(error.localizedDescription)")
240
+ reject("VERIFY_ERROR", error.localizedDescription, error)
241
+ }
242
+ }
213
243
  }
214
244
 
215
245
  @objc
@@ -260,8 +290,7 @@ public class SpotnyBeaconScanner: NSObject {
260
290
  reject: @escaping (String?, String?, Error?) -> Void
261
291
  ) {
262
292
  identifiedUserId = userId
263
- var payload: [String: Any] = ["device_id": getDeviceId(), "user_id": userId]
264
- if let src = source { payload["source"] = src }
293
+ let payload: [String: Any] = ["user_id": userId]
265
294
  post(endpoint: "/api/app/users/identify", payload: payload) { result in
266
295
  switch result {
267
296
  case .success(let (status, _)):
@@ -444,6 +473,10 @@ public class SpotnyBeaconScanner: NSObject {
444
473
  var req = URLRequest(url: url)
445
474
  req.httpMethod = "POST"
446
475
  req.setValue("application/json", forHTTPHeaderField: "Content-Type")
476
+ if let token = sdkToken { req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") }
477
+ // device_id and source injected on every request — backend can verify without parsing the body
478
+ req.setValue(getDeviceId(), forHTTPHeaderField: "X-Device-ID")
479
+ if let src = source { req.setValue(src, forHTTPHeaderField: "X-Source") }
447
480
  req.timeoutInterval = 10.0
448
481
 
449
482
  var bg: UIBackgroundTaskIdentifier = .invalid
@@ -478,7 +511,7 @@ public class SpotnyBeaconScanner: NSObject {
478
511
  return campaignFetchCooldown * pow(3.0, Double(min(retries, 3)))
479
512
  }
480
513
 
481
- private func fetchCampaign(major: Int, minor: Int, deviceId: String) {
514
+ private func fetchCampaign(major: Int, minor: Int) {
482
515
  let key = beaconKey(major: major, minor: minor)
483
516
  guard activeCampaigns[key] == nil else { return }
484
517
  guard fetchInProgress[key] != true else { return }
@@ -491,9 +524,8 @@ public class SpotnyBeaconScanner: NSObject {
491
524
  lastCampaignFetchAttempt[key] = Date()
492
525
  fetchInProgress[key] = true
493
526
 
494
- var payload: [String: Any] = ["beacon_id": key, "device_id": deviceId]
527
+ var payload: [String: Any] = ["beacon_id": key]
495
528
  if let uid = identifiedUserId { payload["user_id"] = uid }
496
- if let src = source { payload["source"] = src }
497
529
 
498
530
  post(endpoint: "/api/app/campaigns/beacon", payload: payload) { [weak self] result in
499
531
  guard let self = self else { return }
@@ -541,7 +573,6 @@ public class SpotnyBeaconScanner: NSObject {
541
573
  eventType: String,
542
574
  key: String,
543
575
  distance: Double,
544
- deviceId: String,
545
576
  endpoint: String
546
577
  ) {
547
578
  let isImpression = eventType == "IMPRESSION_HEARTBEAT"
@@ -564,14 +595,12 @@ public class SpotnyBeaconScanner: NSObject {
564
595
 
565
596
  var payload: [String: Any] = [
566
597
  "event_type": eventType,
567
- "device_id": deviceId,
568
598
  "distance": distance,
569
599
  "screen_id": campaign.screenId
570
600
  ]
571
601
  if let cid = campaign.campaignId { payload["campaign_id"] = cid }
572
602
  if let sid = campaign.sessionId { payload["session_id"] = sid }
573
603
  if let uid = identifiedUserId { payload["user_id"] = uid }
574
- if let src = source { payload["source"] = src }
575
604
 
576
605
  post(endpoint: endpoint, payload: payload) { [weak self] result in
577
606
  guard let self = self else { return }
@@ -608,20 +637,20 @@ public class SpotnyBeaconScanner: NSObject {
608
637
  }
609
638
  }
610
639
 
611
- private func sendProximity(eventType: String, key: String, distance: Double, deviceId: String) {
640
+ private func sendProximity(eventType: String, key: String, distance: Double) {
612
641
  sendTracking(eventType: eventType, key: key, distance: distance,
613
- deviceId: deviceId, endpoint: "/api/app/impressions/proximity")
642
+ endpoint: "/api/app/impressions/proximity")
614
643
  }
615
644
 
616
- private func sendImpression(key: String, distance: Double, deviceId: String) {
645
+ private func sendImpression(key: String, distance: Double) {
617
646
  sendTracking(eventType: "IMPRESSION_HEARTBEAT", key: key, distance: distance,
618
- deviceId: deviceId, endpoint: "/api/app/impressions/track")
647
+ endpoint: "/api/app/impressions/track")
619
648
  }
620
649
 
621
650
  // MARK: - State Cleanup
622
651
 
623
- private func cleanupBeacon(_ key: String, deviceId: String, distance: Double = 0) {
624
- sendProximity(eventType: "PROXIMITY_EXIT", key: key, distance: distance, deviceId: deviceId)
652
+ private func cleanupBeacon(_ key: String, distance: Double = 0) {
653
+ sendProximity(eventType: "PROXIMITY_EXIT", key: key, distance: distance)
625
654
  activeCampaigns.removeValue(forKey: key)
626
655
  lastProximityEventSent.removeValue(forKey: key)
627
656
  lastProximityDistance.removeValue(forKey: key)
@@ -635,12 +664,11 @@ public class SpotnyBeaconScanner: NSObject {
635
664
  }
636
665
 
637
666
  private func cleanupAllProximityState() {
638
- let deviceId = getDeviceId()
639
667
  // Stagger exit events to avoid firing N simultaneous network requests
640
668
  let keys = Array(activeCampaigns.keys)
641
669
  for (index, key) in keys.enumerated() {
642
670
  DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.3) { [weak self] in
643
- self?.cleanupBeacon(key, deviceId: deviceId)
671
+ self?.cleanupBeacon(key)
644
672
  }
645
673
  }
646
674
  }
@@ -669,7 +697,6 @@ extension SpotnyBeaconScanner: KTKBeaconManagerDelegate {
669
697
  didRangeBeacons beacons: [CLBeacon],
670
698
  in region: KTKBeaconRegion
671
699
  ) {
672
- let deviceId = getDeviceId()
673
700
  let now = Date()
674
701
 
675
702
  // Refresh session heartbeat every 60 s to prevent TTL expiry on a live session
@@ -724,13 +751,13 @@ extension SpotnyBeaconScanner: KTKBeaconManagerDelegate {
724
751
  let isFirst = lastProximityEventSent[key] == nil
725
752
 
726
753
  if isFirst {
727
- sendProximity(eventType: "NEARBY", key: key, distance: distance, deviceId: deviceId)
754
+ sendProximity(eventType: "NEARBY", key: key, distance: distance)
728
755
  lastProximityDistance[key] = distance
729
756
  lastProximityEventSent[key] = now
730
757
  } else if distance >= 1.0,
731
758
  let lastDist = lastProximityDistance[key],
732
759
  abs(distance - lastDist) >= proximityDistanceThreshold {
733
- sendProximity(eventType: "NEARBY", key: key, distance: distance, deviceId: deviceId)
760
+ sendProximity(eventType: "NEARBY", key: key, distance: distance)
734
761
  lastProximityDistance[key] = distance
735
762
  lastProximityEventSent[key] = now
736
763
  }
@@ -739,16 +766,16 @@ extension SpotnyBeaconScanner: KTKBeaconManagerDelegate {
739
766
  if let _ = campaign.campaignId, !campaign.inQueue, distance <= impressionDistance {
740
767
  if let last = lastImpressionEventSent[key] {
741
768
  if now.timeIntervalSince(last) >= impressionEventInterval {
742
- sendImpression(key: key, distance: distance, deviceId: deviceId)
769
+ sendImpression(key: key, distance: distance)
743
770
  lastImpressionEventSent[key] = now
744
771
  }
745
772
  } else {
746
- sendImpression(key: key, distance: distance, deviceId: deviceId)
773
+ sendImpression(key: key, distance: distance)
747
774
  lastImpressionEventSent[key] = now
748
775
  }
749
776
  }
750
777
  } else {
751
- fetchCampaign(major: major, minor: minor, deviceId: deviceId)
778
+ fetchCampaign(major: major, minor: minor)
752
779
  }
753
780
  }
754
781
  }
@@ -773,7 +800,7 @@ extension SpotnyBeaconScanner: KTKBeaconManagerDelegate {
773
800
  lastCampaignFetchAttempt.removeValue(forKey: key)
774
801
  fetchRetryCount.removeValue(forKey: key)
775
802
  if activeCampaigns[key] == nil {
776
- fetchCampaign(major: major, minor: minor, deviceId: getDeviceId())
803
+ fetchCampaign(major: major, minor: minor)
777
804
  }
778
805
  }
779
806
  }
@@ -792,7 +819,7 @@ extension SpotnyBeaconScanner: KTKBeaconManagerDelegate {
792
819
 
793
820
  let parts = region.identifier.components(separatedBy: "_")
794
821
  if parts.count == 3, let major = Int(parts[1]), let minor = Int(parts[2]) {
795
- cleanupBeacon(beaconKey(major: major, minor: minor), deviceId: getDeviceId())
822
+ cleanupBeacon(beaconKey(major: major, minor: minor))
796
823
  }
797
824
 
798
825
  beaconManager.stopRangingBeacons(in: region)
@@ -1 +1 @@
1
- {"version":3,"names":["NativeEventEmitter","NativeModules","NativeSpotnySdk","SpotnyEvents","ON_BEACONS_RANGED","ON_BEACON_REGION_EVENT","eventEmitter","SpotnySdk","startScanner","stopScanner","isScanning","initialize","config","identify","userId","requestNotificationPermissions","getDebugLogs","clearDebugLogs","setDebounceInterval","interval","clearDebounceCache","getDebounceStatus","addBeaconsRangedListener","callback","addListener","addBeaconRegionListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,kBAAkB,EAAEC,aAAa,QAAQ,cAAc;AAChE,OAAOC,eAAe,MAAM,sBAAmB;;AAE/C;AACA,OAAO,MAAMC,YAAY,GAAG;EAC1BC,iBAAiB,EAAE,iBAAiB;EACpCC,sBAAsB,EAAE;AAC1B,CAAU;;AAEV;;AAqCA;AACA,MAAMC,YAAY,GAAG,IAAIN,kBAAkB,CAACC,aAAa,CAACM,SAAS,CAAC;;AAEpE;;AAEA;AACA,OAAO,SAASC,YAAYA,CAAA,EAAoB;EAC9C,OAAON,eAAe,CAACM,YAAY,CAAC,CAAC;AACvC;;AAEA;AACA,OAAO,SAASC,WAAWA,CAAA,EAAoB;EAC7C,OAAOP,eAAe,CAACO,WAAW,CAAC,CAAC;AACtC;;AAEA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAqB;EAC7C,OAAOR,eAAe,CAACQ,UAAU,CAAC,CAAC;AACrC;;AAEA;;AAEA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,MAAuB,EAAmB;EACnE,OAAOV,eAAe,CAACS,UAAU,CAACC,MAAgB,CAAC;AACrD;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAACC,MAAc,EAAmB;EACxD,OAAOZ,eAAe,CAACW,QAAQ,CAACC,MAAM,CAAC;AACzC;;AAEA;;AAEA;AACA,OAAO,SAASC,8BAA8BA,CAAA,EAAoB;EAChE,OAAOb,eAAe,CAACa,8BAA8B,CAAC,CAAC;AACzD;;AAEA;;AAEA;AACA,OAAO,SAASC,YAAYA,CAAA,EAAoB;EAC9C,OAAOd,eAAe,CAACc,YAAY,CAAC,CAAC;AACvC;;AAEA;AACA,OAAO,SAASC,cAAcA,CAAA,EAAoB;EAChD,OAAOf,eAAe,CAACe,cAAc,CAAC,CAAC;AACzC;;AAEA;;AAEA;AACA,OAAO,SAASC,mBAAmBA,CAACC,QAAgB,EAAmB;EACrE,OAAOjB,eAAe,CAACgB,mBAAmB,CAACC,QAAQ,CAAC;AACtD;;AAEA;AACA,OAAO,SAASC,kBAAkBA,CAAA,EAAoB;EACpD,OAAOlB,eAAe,CAACkB,kBAAkB,CAAC,CAAC;AAC7C;;AAEA;AACA,OAAO,SAASC,iBAAiBA,CAAA,EAAoB;EACnD,OAAOnB,eAAe,CAACmB,iBAAiB,CAAC,CAAC;AAC5C;;AAEA;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CACtCC,QAA4C,EAC5C;EACA,OAAOjB,YAAY,CAACkB,WAAW,CAC7BrB,YAAY,CAACC,iBAAiB,EAC9BmB,QACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASE,uBAAuBA,CACrCF,QAA4C,EAC5C;EACA,OAAOjB,YAAY,CAACkB,WAAW,CAC7BrB,YAAY,CAACE,sBAAsB,EACnCkB,QACF,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["NativeEventEmitter","NativeModules","NativeSpotnySdk","SpotnyEvents","ON_BEACONS_RANGED","ON_BEACON_REGION_EVENT","eventEmitter","SpotnySdk","startScanner","stopScanner","isScanning","initialize","config","identify","userId","requestNotificationPermissions","getDebugLogs","clearDebugLogs","setDebounceInterval","interval","clearDebounceCache","getDebounceStatus","addBeaconsRangedListener","callback","addListener","addBeaconRegionListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,kBAAkB,EAAEC,aAAa,QAAQ,cAAc;AAChE,OAAOC,eAAe,MAAM,sBAAmB;;AAE/C;AACA,OAAO,MAAMC,YAAY,GAAG;EAC1BC,iBAAiB,EAAE,iBAAiB;EACpCC,sBAAsB,EAAE;AAC1B,CAAU;;AAEV;;AA0CA;AACA,MAAMC,YAAY,GAAG,IAAIN,kBAAkB,CAACC,aAAa,CAACM,SAAS,CAAC;;AAEpE;;AAEA;AACA,OAAO,SAASC,YAAYA,CAAA,EAAoB;EAC9C,OAAON,eAAe,CAACM,YAAY,CAAC,CAAC;AACvC;;AAEA;AACA,OAAO,SAASC,WAAWA,CAAA,EAAoB;EAC7C,OAAOP,eAAe,CAACO,WAAW,CAAC,CAAC;AACtC;;AAEA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAqB;EAC7C,OAAOR,eAAe,CAACQ,UAAU,CAAC,CAAC;AACrC;;AAEA;;AAEA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,MAAuB,EAAmB;EACnE,OAAOV,eAAe,CAACS,UAAU,CAACC,MAAgB,CAAC;AACrD;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAACC,MAAc,EAAmB;EACxD,OAAOZ,eAAe,CAACW,QAAQ,CAACC,MAAM,CAAC;AACzC;;AAEA;;AAEA;AACA,OAAO,SAASC,8BAA8BA,CAAA,EAAoB;EAChE,OAAOb,eAAe,CAACa,8BAA8B,CAAC,CAAC;AACzD;;AAEA;;AAEA;AACA,OAAO,SAASC,YAAYA,CAAA,EAAoB;EAC9C,OAAOd,eAAe,CAACc,YAAY,CAAC,CAAC;AACvC;;AAEA;AACA,OAAO,SAASC,cAAcA,CAAA,EAAoB;EAChD,OAAOf,eAAe,CAACe,cAAc,CAAC,CAAC;AACzC;;AAEA;;AAEA;AACA,OAAO,SAASC,mBAAmBA,CAACC,QAAgB,EAAmB;EACrE,OAAOjB,eAAe,CAACgB,mBAAmB,CAACC,QAAQ,CAAC;AACtD;;AAEA;AACA,OAAO,SAASC,kBAAkBA,CAAA,EAAoB;EACpD,OAAOlB,eAAe,CAACkB,kBAAkB,CAAC,CAAC;AAC7C;;AAEA;AACA,OAAO,SAASC,iBAAiBA,CAAA,EAAoB;EACnD,OAAOnB,eAAe,CAACmB,iBAAiB,CAAC,CAAC;AAC5C;;AAEA;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CACtCC,QAA4C,EAC5C;EACA,OAAOjB,YAAY,CAACkB,WAAW,CAC7BrB,YAAY,CAACC,iBAAiB,EAC9BmB,QACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASE,uBAAuBA,CACrCF,QAA4C,EAC5C;EACA,OAAOjB,YAAY,CAACkB,WAAW,CAC7BrB,YAAY,CAACE,sBAAsB,EACnCkB,QACF,CAAC;AACH","ignoreList":[]}
@@ -23,10 +23,15 @@ export type BeaconRegionEvent = {
23
23
  state?: 'inside' | 'outside' | 'unknown';
24
24
  };
25
25
  export type SpotnySdkConfig = {
26
- /** Maximum BLE detection distance in metres (default: 8.0) */
27
- maxDetectionDistance?: number;
26
+ /**
27
+ * SDK token issued by Spotny for your app.
28
+ * Verified against the backend during initialize(). Required.
29
+ */
30
+ token: string;
28
31
  /** Identifier for your brand or app (e.g. 'nike'). */
29
32
  source?: string;
33
+ /** Maximum BLE detection distance in metres (default: 8.0) */
34
+ maxDetectionDistance?: number;
30
35
  /**
31
36
  * Multiplier applied to raw RSSI-derived distance to compensate for device
32
37
  * TX power variance (default: 0.5, tuned for Kontakt.io -12 dBm beacons).
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAIX,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;IACvC,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAOF,6BAA6B;AAC7B,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAE9C;AAED,mDAAmD;AACnD,wBAAgB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAE7C;AAED,uDAAuD;AACvD,wBAAgB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAE7C;AAID;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;AAID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExD;AAID,gEAAgE;AAChE,wBAAgB,8BAA8B,IAAI,OAAO,CAAC,MAAM,CAAC,CAEhE;AAID,yCAAyC;AACzC,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAE9C;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAEhD;AAID,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEpD;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEnD;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,4CAM7C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,4CAM7C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAIX,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;IACvC,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAOF,6BAA6B;AAC7B,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAE9C;AAED,mDAAmD;AACnD,wBAAgB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAE7C;AAED,uDAAuD;AACvD,wBAAgB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAE7C;AAID;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;AAID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExD;AAID,gEAAgE;AAChE,wBAAgB,8BAA8B,IAAI,OAAO,CAAC,MAAM,CAAC,CAEhE;AAID,yCAAyC;AACzC,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAE9C;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAEhD;AAID,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEpD;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEnD;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,4CAM7C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,4CAM7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spotny-sdk",
3
- "version": "0.5.5",
3
+ "version": "0.6.2",
4
4
  "description": "Beacon Scanner",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
package/src/index.tsx CHANGED
@@ -33,10 +33,15 @@ export type BeaconRegionEvent = {
33
33
  };
34
34
 
35
35
  export type SpotnySdkConfig = {
36
- /** Maximum BLE detection distance in metres (default: 8.0) */
37
- maxDetectionDistance?: number;
36
+ /**
37
+ * SDK token issued by Spotny for your app.
38
+ * Verified against the backend during initialize(). Required.
39
+ */
40
+ token: string;
38
41
  /** Identifier for your brand or app (e.g. 'nike'). */
39
42
  source?: string;
43
+ /** Maximum BLE detection distance in metres (default: 8.0) */
44
+ maxDetectionDistance?: number;
40
45
  /**
41
46
  * Multiplier applied to raw RSSI-derived distance to compensate for device
42
47
  * TX power variance (default: 0.5, tuned for Kontakt.io -12 dBm beacons).