@doorstepai/dropoff-sdk 2.0.7 → 2.0.9

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 (39) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/com/doorstepai/dropoffsdk/DoorstepAIModule.kt +168 -7
  3. package/ios/DoorstepAI.mm +25 -1
  4. package/ios/DoorstepAI.swift +147 -3
  5. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/DoorstepDropoffSDK +0 -0
  6. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/Info.plist +0 -0
  7. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios.abi.json +5436 -1479
  8. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +93 -9
  9. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  10. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios.swiftinterface +93 -9
  11. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/DoorstepDropoffSDK.framework/_CodeSignature/CodeResources +11 -11
  12. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/dSYMs/DoorstepDropoffSDK.framework.dSYM/Contents/Resources/DWARF/DoorstepDropoffSDK +0 -0
  13. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64/dSYMs/DoorstepDropoffSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/DoorstepDropoffSDK.yml +2560 -2106
  14. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/DoorstepDropoffSDK +0 -0
  15. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Info.plist +0 -0
  16. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +5436 -1479
  17. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +93 -9
  18. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  19. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +93 -9
  20. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +5436 -1479
  21. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +93 -9
  22. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  23. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/Modules/DoorstepDropoffSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +93 -9
  24. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/DoorstepDropoffSDK.framework/_CodeSignature/CodeResources +21 -21
  25. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/DoorstepDropoffSDK.framework.dSYM/Contents/Resources/DWARF/DoorstepDropoffSDK +0 -0
  26. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/DoorstepDropoffSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/DoorstepDropoffSDK.yml +2560 -2106
  27. package/ios/DoorstepDropoffSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/DoorstepDropoffSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/DoorstepDropoffSDK.yml +2551 -2096
  28. package/lib/module/DoorstepAI.js +107 -12
  29. package/lib/module/DoorstepAI.js.map +1 -1
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/typescript/src/DoorstepAI.d.ts +75 -2
  32. package/lib/typescript/src/DoorstepAI.d.ts.map +1 -1
  33. package/lib/typescript/src/RootDoorstepAI.d.ts +1 -1
  34. package/lib/typescript/src/RootDoorstepAI.d.ts.map +1 -1
  35. package/lib/typescript/src/index.d.ts +1 -1
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/DoorstepAI.tsx +199 -18
  39. package/src/index.tsx +4 -0
@@ -86,7 +86,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
86
86
  dependencies {
87
87
  implementation "com.facebook.react:react-android"
88
88
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
89
- implementation("ai.doorstep.com:doorstepai-dropoff-sdk:2.0.8")
89
+ implementation("ai.doorstep.com:doorstepai-dropoff-sdk:2.0.13")
90
90
  api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
91
91
  }
92
92
 
@@ -11,9 +11,19 @@ import com.doorstepai.sdks.tracking.AddressType
11
11
  import com.doorstepai.sdks.tracking.DropoffType
12
12
  import com.doorstepai.sdks.tracking.LatLngObject
13
13
  import com.facebook.react.bridge.ReadableMap
14
+ import com.facebook.react.bridge.ReadableArray
15
+ import com.facebook.react.bridge.WritableMap
16
+ import com.facebook.react.bridge.Arguments
17
+ import com.facebook.react.modules.core.DeviceEventManagerModule
18
+ import com.doorstepai.sdks.tracking.DeliveryStop
19
+ import com.doorstepai.sdks.tracking.RouteGeofenceOptions
14
20
  import kotlinx.coroutines.Dispatchers
15
21
  import kotlinx.coroutines.CoroutineScope
22
+ import kotlinx.coroutines.SupervisorJob
23
+ import kotlinx.coroutines.Job
16
24
  import kotlinx.coroutines.launch
25
+ import kotlinx.coroutines.flow.launchIn
26
+ import kotlinx.coroutines.flow.onEach
17
27
 
18
28
 
19
29
  class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
@@ -45,6 +55,8 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
45
55
  timeoutSeconds: Double?,
46
56
  autoStopAfterDropoffSeconds: Double?,
47
57
  manualForeground: Boolean?,
58
+ customerId: String?,
59
+ driverId: String?,
48
60
  promise: Promise
49
61
  ) {
50
62
  try {
@@ -53,7 +65,9 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
53
65
  deliveryId = deliveryId,
54
66
  timeoutSeconds = timeoutSeconds,
55
67
  manualForeground = manualForeground ?: false,
56
- autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds
68
+ autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds,
69
+ customerId = customerId,
70
+ driverId = driverId
57
71
  ) { result ->
58
72
  result.onSuccess { sessionId ->
59
73
  promise.resolve(sessionId)
@@ -71,10 +85,12 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
71
85
  plusCode: String,
72
86
  deliveryId: String,
73
87
  timeoutSeconds: Double?,
88
+ customerId: String?,
89
+ driverId: String?,
74
90
  promise: Promise
75
91
  ) {
76
92
  try {
77
- DoorstepAI.startDeliveryByPlusCode(plusCode, deliveryId, timeoutSeconds) { result ->
93
+ DoorstepAI.startDeliveryByPlusCode(plusCode, deliveryId, timeoutSeconds, customerId, driverId) { result ->
78
94
  result.onSuccess { sessionId ->
79
95
  promise.resolve(sessionId)
80
96
  }.onFailure { error ->
@@ -94,6 +110,8 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
94
110
  timeoutSeconds: Double?,
95
111
  autoStopAfterDropoffSeconds: Double?,
96
112
  manualForeground: Boolean?,
113
+ customerId: String?,
114
+ driverId: String?,
97
115
  promise: Promise
98
116
  ) {
99
117
  try {
@@ -104,7 +122,9 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
104
122
  coordinates = coords,
105
123
  timeoutSeconds = timeoutSeconds,
106
124
  manualForeground = manualForeground ?: false,
107
- autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds
125
+ autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds,
126
+ customerId = customerId,
127
+ driverId = driverId
108
128
  ) { result ->
109
129
  result.onSuccess { message ->
110
130
  promise.resolve(message)
@@ -124,10 +144,12 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
124
144
  subUnit: String,
125
145
  deliveryId: String,
126
146
  timeoutSeconds: Double?,
147
+ customerId: String?,
148
+ driverId: String?,
127
149
  promise: Promise
128
150
  ) {
129
151
  try {
130
- DoorstepAI.startDeliveryByLatLng(latitude, longitude, subUnit, deliveryId, timeoutSeconds) { result ->
152
+ DoorstepAI.startDeliveryByLatLng(latitude, longitude, subUnit, deliveryId, timeoutSeconds, customerId, driverId) { result ->
131
153
  result.onSuccess { message ->
132
154
  promise.resolve(message)
133
155
  }.onFailure { error ->
@@ -147,6 +169,8 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
147
169
  timeoutSeconds: Double?,
148
170
  autoStopAfterDropoffSeconds: Double?,
149
171
  manualForeground: Boolean?,
172
+ customerId: String?,
173
+ driverId: String?,
150
174
  promise: Promise
151
175
  ) {
152
176
  try {
@@ -173,7 +197,9 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
173
197
  coordinates = coords,
174
198
  timeoutSeconds = timeoutSeconds,
175
199
  manualForeground = manualForeground ?: false,
176
- autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds
200
+ autoStopAfterDropoffSeconds = autoStopAfterDropoffSeconds,
201
+ customerId = customerId,
202
+ driverId = driverId
177
203
  ) { result ->
178
204
  result.onSuccess { sessionId ->
179
205
  promise.resolve(sessionId)
@@ -187,9 +213,9 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
187
213
  }
188
214
 
189
215
  @ReactMethod
190
- fun newEvent(eventName: String, deliveryId: String, promise: Promise) {
216
+ fun newEvent(eventName: String, deliveryId: String, timestamp: Double?, promise: Promise) {
191
217
  try {
192
- DoorstepAI.newEvent(eventName, deliveryId) { /* Callback might not be invoked or provide useful data */ }
218
+ DoorstepAI.newEvent(eventName, deliveryId, timestamp) { /* Callback not needed here */ }
193
219
  promise.resolve("Event $eventName triggered for $deliveryId")
194
220
  } catch (e: Exception) {
195
221
  promise.reject("EVENT_CREATION_ERROR", e.message ?: "Failed to save event")
@@ -273,7 +299,142 @@ class DoorstepAIModule(private val reactContext: ReactApplicationContext) :
273
299
  return LatLngObject(lat = lat, lng = lng)
274
300
  }
275
301
 
302
+ // ---- Route geofencing ----
303
+
304
+ private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
305
+ private var geofenceJob: Job? = null
306
+
307
+ @ReactMethod
308
+ fun startRouteGeofencing(stops: ReadableArray, options: ReadableMap?, promise: Promise) {
309
+ try {
310
+ val parsed = parseStops(stops)
311
+ if (parsed.isEmpty()) {
312
+ promise.reject(
313
+ "ROUTE_GEOFENCING_ERROR",
314
+ "startRouteGeofencing requires at least one valid stop (deliveryId + latitude + longitude)"
315
+ )
316
+ return
317
+ }
318
+ DoorstepAI.startRouteGeofencing(parsed, parseRouteOptions(options)) { result ->
319
+ result.onSuccess { promise.resolve(true) }
320
+ .onFailure { promise.reject("ROUTE_GEOFENCING_ERROR", it.message ?: "Failed to start route geofencing") }
321
+ }
322
+ } catch (e: Exception) {
323
+ promise.reject("ROUTE_GEOFENCING_ERROR", e.message ?: "Failed to start route geofencing")
324
+ }
325
+ }
326
+
327
+ @ReactMethod
328
+ fun stopRouteGeofencing(promise: Promise) {
329
+ try {
330
+ DoorstepAI.stopRouteGeofencing()
331
+ promise.resolve(true)
332
+ } catch (e: Exception) {
333
+ promise.reject("ROUTE_GEOFENCING_ERROR", e.message ?: "Failed to stop route geofencing")
334
+ }
335
+ }
336
+
337
+ @ReactMethod
338
+ fun resumeRouteGeofencingIfNeeded(promise: Promise) {
339
+ try {
340
+ DoorstepAI.resumeRouteGeofencingIfNeeded()
341
+ promise.resolve(true)
342
+ } catch (e: Exception) {
343
+ promise.reject("ROUTE_GEOFENCING_ERROR", e.message ?: "Failed to resume route geofencing")
344
+ }
345
+ }
346
+
347
+ @ReactMethod
348
+ fun getMonitoredStops(promise: Promise) {
349
+ try {
350
+ val arr = Arguments.createArray()
351
+ DoorstepAI.monitoredStops.forEach { arr.pushMap(serializeStop(it)) }
352
+ promise.resolve(arr)
353
+ } catch (e: Exception) {
354
+ promise.reject("ROUTE_GEOFENCING_ERROR", e.message ?: "Failed to get monitored stops")
355
+ }
356
+ }
357
+
358
+ // ---- Geofence-session event stream (NativeEventEmitter) ----
359
+
360
+ @ReactMethod
361
+ fun addListener(eventName: String) {
362
+ // On the first JS subscription, bridge the SDK's geofenceSessionEvents Flow
363
+ // to JS. A single collector serves the module lifetime (cancelled in invalidate()).
364
+ if (geofenceJob == null) {
365
+ geofenceJob = DoorstepAI.geofenceSessionEvents
366
+ .onEach { ev ->
367
+ val map = Arguments.createMap().apply {
368
+ putString("deliveryId", ev.deliveryId)
369
+ putString("type", ev.type.name) // "STARTED" | "STOPPED"
370
+ putString("reason", ev.reason)
371
+ }
372
+ reactContext
373
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
374
+ .emit(GEOFENCE_EVENT, map)
375
+ }
376
+ .launchIn(moduleScope)
377
+ }
378
+ }
379
+
380
+ @ReactMethod
381
+ fun removeListeners(count: Int) {
382
+ // Required stub for NativeEventEmitter on Android. The single Flow collector
383
+ // is torn down in invalidate(); no per-listener teardown is needed.
384
+ }
385
+
386
+ override fun invalidate() {
387
+ geofenceJob?.cancel()
388
+ geofenceJob = null
389
+ super.invalidate()
390
+ }
391
+
392
+ private fun parseStops(arr: ReadableArray): List<DeliveryStop> {
393
+ val out = mutableListOf<DeliveryStop>()
394
+ for (i in 0 until arr.size()) {
395
+ val m = arr.getMap(i) ?: continue
396
+ val deliveryId = if (m.hasKey("deliveryId")) m.getString("deliveryId") else null
397
+ if (deliveryId.isNullOrEmpty()) continue
398
+ if (!m.hasKey("latitude") || !m.hasKey("longitude")) continue
399
+ out.add(
400
+ DeliveryStop(
401
+ deliveryId = deliveryId,
402
+ address = if (m.hasKey("address")) m.getString("address") ?: "" else "",
403
+ latitude = m.getDouble("latitude"),
404
+ longitude = m.getDouble("longitude"),
405
+ radiusMeters = if (m.hasKey("radiusMeters") && !m.isNull("radiusMeters")) m.getDouble("radiusMeters") else null,
406
+ customerId = if (m.hasKey("customerId")) m.getString("customerId") else null,
407
+ driverId = if (m.hasKey("driverId")) m.getString("driverId") else null
408
+ )
409
+ )
410
+ }
411
+ return out
412
+ }
413
+
414
+ private fun parseRouteOptions(m: ReadableMap?): RouteGeofenceOptions {
415
+ if (m == null) return RouteGeofenceOptions()
416
+ return RouteGeofenceOptions(
417
+ defaultRadiusMeters = if (m.hasKey("defaultRadiusMeters") && !m.isNull("defaultRadiusMeters")) m.getDouble("defaultRadiusMeters") else 250.0,
418
+ timeoutSeconds = if (m.hasKey("timeoutSeconds") && !m.isNull("timeoutSeconds")) m.getDouble("timeoutSeconds") else null,
419
+ autoStopAfterDropoffSeconds = if (m.hasKey("autoStopAfterDropoffSeconds") && !m.isNull("autoStopAfterDropoffSeconds")) m.getDouble("autoStopAfterDropoffSeconds") else null,
420
+ manualForeground = if (m.hasKey("manualForeground") && !m.isNull("manualForeground")) m.getBoolean("manualForeground") else false
421
+ )
422
+ }
423
+
424
+ private fun serializeStop(s: DeliveryStop): WritableMap {
425
+ val m = Arguments.createMap()
426
+ m.putString("deliveryId", s.deliveryId)
427
+ m.putString("address", s.address)
428
+ m.putDouble("latitude", s.latitude)
429
+ m.putDouble("longitude", s.longitude)
430
+ s.radiusMeters?.let { m.putDouble("radiusMeters", it) }
431
+ s.customerId?.let { m.putString("customerId", it) }
432
+ s.driverId?.let { m.putString("driverId", it) }
433
+ return m
434
+ }
435
+
276
436
  companion object {
277
437
  const val NAME = "DoorstepAI"
438
+ private const val GEOFENCE_EVENT = "DoorstepGeofenceSessionEvent"
278
439
  }
279
440
  }
package/ios/DoorstepAI.mm CHANGED
@@ -1,6 +1,7 @@
1
1
  #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
2
3
 
3
- @interface RCT_EXTERN_MODULE(DoorstepAI, NSObject)
4
+ @interface RCT_EXTERN_MODULE(DoorstepAI, RCTEventEmitter)
4
5
 
5
6
  RCT_EXTERN_METHOD(setApiKey:(nonnull NSString *)apiKey)
6
7
  RCT_EXTERN_METHOD(requestAllPermissions:(BOOL)requestAlwaysLocation)
@@ -16,12 +17,16 @@ RCT_EXTERN_METHOD(startDeliveryByPlaceID:(nonnull NSString *)placeID
16
17
  deliveryId:(nonnull NSString *)deliveryId
17
18
  timeoutSeconds:(id)timeoutSeconds
18
19
  autoStopAfterDropoffSeconds:(id)autoStopAfterDropoffSeconds
20
+ customerId:(nullable NSString *)customerId
21
+ driverId:(nullable NSString *)driverId
19
22
  resolver:(RCTPromiseResolveBlock)resolve
20
23
  rejecter:(RCTPromiseRejectBlock)reject)
21
24
  RCT_EXTERN_METHOD(startDeliveryByPlusCode:(nonnull NSString *)plusCode
22
25
  deliveryId:(nonnull NSString *)deliveryId
23
26
  timeoutSeconds:(id)timeoutSeconds
24
27
  autoStopAfterDropoffSeconds:(id)autoStopAfterDropoffSeconds
28
+ customerId:(nullable NSString *)customerId
29
+ driverId:(nullable NSString *)driverId
25
30
  resolver:(RCTPromiseResolveBlock)resolve
26
31
  rejecter:(RCTPromiseRejectBlock)reject)
27
32
  RCT_EXTERN_METHOD(startDeliveryByAddress:(nonnull NSDictionary *)address
@@ -29,6 +34,8 @@ RCT_EXTERN_METHOD(startDeliveryByAddress:(nonnull NSDictionary *)address
29
34
  coordinates:(nullable NSDictionary *)coordinates
30
35
  timeoutSeconds:(id)timeoutSeconds
31
36
  autoStopAfterDropoffSeconds:(id)autoStopAfterDropoffSeconds
37
+ customerId:(nullable NSString *)customerId
38
+ driverId:(nullable NSString *)driverId
32
39
  resolver:(RCTPromiseResolveBlock)resolve
33
40
  rejecter:(RCTPromiseRejectBlock)reject)
34
41
  RCT_EXTERN_METHOD(startDeliveryByAddressString:(nonnull NSString *)address
@@ -36,6 +43,8 @@ RCT_EXTERN_METHOD(startDeliveryByAddressString:(nonnull NSString *)address
36
43
  coordinates:(nullable NSDictionary *)coordinates
37
44
  timeoutSeconds:(id)timeoutSeconds
38
45
  autoStopAfterDropoffSeconds:(id)autoStopAfterDropoffSeconds
46
+ customerId:(nullable NSString *)customerId
47
+ driverId:(nullable NSString *)driverId
39
48
  resolver:(RCTPromiseResolveBlock)resolve
40
49
  rejecter:(RCTPromiseRejectBlock)reject)
41
50
  RCT_EXTERN_METHOD(startDeliveryByLatLng:(nonnull NSNumber *)latitude
@@ -44,10 +53,13 @@ RCT_EXTERN_METHOD(startDeliveryByLatLng:(nonnull NSNumber *)latitude
44
53
  deliveryId:(nonnull NSString *)deliveryId
45
54
  timeoutSeconds:(id)timeoutSeconds
46
55
  autoStopAfterDropoffSeconds:(id)autoStopAfterDropoffSeconds
56
+ customerId:(nullable NSString *)customerId
57
+ driverId:(nullable NSString *)driverId
47
58
  resolver:(RCTPromiseResolveBlock)resolve
48
59
  rejecter:(RCTPromiseRejectBlock)reject)
49
60
  RCT_EXTERN_METHOD(newEvent:(nonnull NSString *)eventName
50
61
  deliveryId:(nonnull NSString *)deliveryId
62
+ timestamp:(id)timestamp
51
63
  resolver:(RCTPromiseResolveBlock)resolve
52
64
  rejecter:(RCTPromiseRejectBlock)reject)
53
65
  RCT_EXTERN_METHOD(markDropoff:(nonnull NSString *)deliveryId
@@ -58,4 +70,16 @@ RCT_EXTERN_METHOD(stopDelivery:(nonnull NSString *)deliveryId
58
70
  resolver:(RCTPromiseResolveBlock)resolve
59
71
  rejecter:(RCTPromiseRejectBlock)reject)
60
72
 
73
+ // ---- Route geofencing ----
74
+ RCT_EXTERN_METHOD(startRouteGeofencing:(nonnull NSArray *)stops
75
+ options:(nullable NSDictionary *)options
76
+ resolver:(RCTPromiseResolveBlock)resolve
77
+ rejecter:(RCTPromiseRejectBlock)reject)
78
+ RCT_EXTERN_METHOD(stopRouteGeofencing:(RCTPromiseResolveBlock)resolve
79
+ rejecter:(RCTPromiseRejectBlock)reject)
80
+ RCT_EXTERN_METHOD(resumeRouteGeofencingIfNeeded:(RCTPromiseResolveBlock)resolve
81
+ rejecter:(RCTPromiseRejectBlock)reject)
82
+ RCT_EXTERN_METHOD(getMonitoredStops:(RCTPromiseResolveBlock)resolve
83
+ rejecter:(RCTPromiseRejectBlock)reject)
84
+
61
85
  @end
@@ -3,12 +3,48 @@ import React
3
3
  import DoorstepDropoffSDK
4
4
 
5
5
  @objc(DoorstepAI)
6
- class DoorstepAIBridge: NSObject {
6
+ class DoorstepAIBridge: RCTEventEmitter {
7
7
  @objc
8
- static func requiresMainQueueSetup() -> Bool {
8
+ override static func requiresMainQueueSetup() -> Bool {
9
9
  return true
10
10
  }
11
11
 
12
+ // MARK: - Geofence session events (RCTEventEmitter)
13
+
14
+ private static let geofenceEventName = "DoorstepGeofenceSessionEvent"
15
+ private var geofenceObservers: [NSObjectProtocol] = []
16
+
17
+ override func supportedEvents() -> [String]! {
18
+ return [DoorstepAIBridge.geofenceEventName]
19
+ }
20
+
21
+ /// Called by RN when the first JS listener subscribes: bridge the SDK's
22
+ /// NotificationCenter geofence notifications to `sendEvent`.
23
+ override func startObserving() {
24
+ let center = NotificationCenter.default
25
+ let started = center.addObserver(
26
+ forName: DoorstepAI.geofenceSessionStartedNotification, object: nil, queue: .main
27
+ ) { [weak self] note in self?.emitGeofenceEvent(type: "STARTED", userInfo: note.userInfo) }
28
+ let stopped = center.addObserver(
29
+ forName: DoorstepAI.geofenceSessionStoppedNotification, object: nil, queue: .main
30
+ ) { [weak self] note in self?.emitGeofenceEvent(type: "STOPPED", userInfo: note.userInfo) }
31
+ geofenceObservers = [started, stopped]
32
+ }
33
+
34
+ /// Called by RN when the last JS listener unsubscribes.
35
+ override func stopObserving() {
36
+ let center = NotificationCenter.default
37
+ geofenceObservers.forEach { center.removeObserver($0) }
38
+ geofenceObservers.removeAll()
39
+ }
40
+
41
+ private func emitGeofenceEvent(type: String, userInfo: [AnyHashable: Any]?) {
42
+ let deliveryId = (userInfo?["deliveryId"] as? String) ?? ""
43
+ let reason = (userInfo?["reason"] as? String) ?? ""
44
+ sendEvent(withName: DoorstepAIBridge.geofenceEventName,
45
+ body: ["deliveryId": deliveryId, "type": type, "reason": reason])
46
+ }
47
+
12
48
  @objc
13
49
  func enableDevMode(_ apiKey: NSString,
14
50
  resolver resolve: @escaping RCTPromiseResolveBlock,
@@ -53,6 +89,8 @@ class DoorstepAIBridge: NSObject {
53
89
  deliveryId: NSString,
54
90
  timeoutSeconds: Any?,
55
91
  autoStopAfterDropoffSeconds: Any?,
92
+ customerId: NSString?,
93
+ driverId: NSString?,
56
94
  resolver resolve: @escaping RCTPromiseResolveBlock,
57
95
  rejecter reject: @escaping RCTPromiseRejectBlock) {
58
96
  // Use Swift's concurrency support to call the async method.
@@ -63,6 +101,8 @@ class DoorstepAIBridge: NSObject {
63
101
  try await DoorstepAI.startDeliveryByPlaceID(
64
102
  placeID: placeID as String,
65
103
  deliveryId: deliveryId as String,
104
+ customerId: customerId as String?,
105
+ driverId: driverId as String?,
66
106
  timeoutSeconds: timeout,
67
107
  autoStopAfterDropoffSeconds: autoStop
68
108
  )
@@ -78,6 +118,8 @@ class DoorstepAIBridge: NSObject {
78
118
  deliveryId: NSString,
79
119
  timeoutSeconds: Any?,
80
120
  autoStopAfterDropoffSeconds: Any?,
121
+ customerId: NSString?,
122
+ driverId: NSString?,
81
123
  resolver resolve: @escaping RCTPromiseResolveBlock,
82
124
  rejecter reject: @escaping RCTPromiseRejectBlock) {
83
125
  Task {
@@ -87,6 +129,8 @@ class DoorstepAIBridge: NSObject {
87
129
  try await DoorstepAI.startDeliveryByPlusCode(
88
130
  plusCode: plusCode as String,
89
131
  deliveryId: deliveryId as String,
132
+ customerId: customerId as String?,
133
+ driverId: driverId as String?,
90
134
  timeoutSeconds: timeout,
91
135
  autoStopAfterDropoffSeconds: autoStop
92
136
  )
@@ -103,6 +147,8 @@ class DoorstepAIBridge: NSObject {
103
147
  coordinates: NSDictionary?,
104
148
  timeoutSeconds: Any?,
105
149
  autoStopAfterDropoffSeconds: Any?,
150
+ customerId: NSString?,
151
+ driverId: NSString?,
106
152
  resolver resolve: @escaping RCTPromiseResolveBlock,
107
153
  rejecter reject: @escaping RCTPromiseRejectBlock) {
108
154
  Task {
@@ -113,6 +159,8 @@ class DoorstepAIBridge: NSObject {
113
159
  try await DoorstepAI.startDeliveryByAddressString(
114
160
  address: address as String,
115
161
  deliveryId: deliveryId as String,
162
+ customerId: customerId as String?,
163
+ driverId: driverId as String?,
116
164
  coordinates: coords,
117
165
  timeoutSeconds: timeout,
118
166
  autoStopAfterDropoffSeconds: autoStop
@@ -131,6 +179,8 @@ class DoorstepAIBridge: NSObject {
131
179
  deliveryId: NSString,
132
180
  timeoutSeconds: Any?,
133
181
  autoStopAfterDropoffSeconds: Any?,
182
+ customerId: NSString?,
183
+ driverId: NSString?,
134
184
  resolver resolve: @escaping RCTPromiseResolveBlock,
135
185
  rejecter reject: @escaping RCTPromiseRejectBlock) {
136
186
  Task {
@@ -142,6 +192,8 @@ class DoorstepAIBridge: NSObject {
142
192
  longitude: longitude.doubleValue,
143
193
  subUnit: subUnit as String,
144
194
  deliveryId: deliveryId as String,
195
+ customerId: customerId as String?,
196
+ driverId: driverId as String?,
145
197
  timeoutSeconds: timeout,
146
198
  autoStopAfterDropoffSeconds: autoStop
147
199
  )
@@ -158,6 +210,8 @@ class DoorstepAIBridge: NSObject {
158
210
  coordinates: NSDictionary?,
159
211
  timeoutSeconds: Any?,
160
212
  autoStopAfterDropoffSeconds: Any?,
213
+ customerId: NSString?,
214
+ driverId: NSString?,
161
215
  resolver resolve: @escaping RCTPromiseResolveBlock,
162
216
  rejecter reject: @escaping RCTPromiseRejectBlock) {
163
217
  Task {
@@ -185,6 +239,8 @@ class DoorstepAIBridge: NSObject {
185
239
  try await DoorstepAI.startDeliveryByAddressType(
186
240
  address: addressStruct,
187
241
  deliveryId: deliveryId as String,
242
+ customerId: customerId as String?,
243
+ driverId: driverId as String?,
188
244
  coordinates: coords,
189
245
  timeoutSeconds: timeout,
190
246
  autoStopAfterDropoffSeconds: autoStop
@@ -201,11 +257,13 @@ class DoorstepAIBridge: NSObject {
201
257
  @objc
202
258
  func newEvent(_ eventName: NSString,
203
259
  deliveryId: NSString,
260
+ timestamp: Any?,
204
261
  resolver resolve: @escaping RCTPromiseResolveBlock,
205
262
  rejecter reject: @escaping RCTPromiseRejectBlock) {
206
263
  Task {
207
264
  do {
208
- try await DoorstepAI.newEvent(eventName: eventName as String, deliveryId: deliveryId as String)
265
+ let ts = (timestamp as? NSNumber)?.doubleValue
266
+ try await DoorstepAI.newEvent(eventName: eventName as String, deliveryId: deliveryId as String, timestamp: ts)
209
267
  resolve(nil)
210
268
  } catch {
211
269
  reject("E_NEW_EVENT", "Failed to send event: \(error.localizedDescription)", error)
@@ -250,6 +308,92 @@ class DoorstepAIBridge: NSObject {
250
308
  }
251
309
  }
252
310
 
311
+ // MARK: - Route geofencing
312
+
313
+ @objc
314
+ func startRouteGeofencing(_ stops: NSArray,
315
+ options: NSDictionary?,
316
+ resolver resolve: @escaping RCTPromiseResolveBlock,
317
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
318
+ let parsed: [DeliveryStop] = (stops as? [[String: Any]] ?? []).compactMap { parseDeliveryStop($0) }
319
+ if parsed.isEmpty {
320
+ reject("E_ROUTE_GEOFENCING", "startRouteGeofencing requires at least one valid stop (deliveryId + latitude + longitude)", nil)
321
+ return
322
+ }
323
+ let opts = parseRouteOptions(options)
324
+ DispatchQueue.main.async {
325
+ DoorstepAI.startRouteGeofencing(stops: parsed, options: opts)
326
+ resolve(nil)
327
+ }
328
+ }
329
+
330
+ @objc
331
+ func stopRouteGeofencing(_ resolve: @escaping RCTPromiseResolveBlock,
332
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
333
+ DispatchQueue.main.async {
334
+ DoorstepAI.stopRouteGeofencing()
335
+ resolve(nil)
336
+ }
337
+ }
338
+
339
+ @objc
340
+ func resumeRouteGeofencingIfNeeded(_ resolve: @escaping RCTPromiseResolveBlock,
341
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
342
+ DispatchQueue.main.async {
343
+ DoorstepAI.resumeRouteGeofencingIfNeeded()
344
+ resolve(nil)
345
+ }
346
+ }
347
+
348
+ @objc
349
+ func getMonitoredStops(_ resolve: @escaping RCTPromiseResolveBlock,
350
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
351
+ DispatchQueue.main.async {
352
+ resolve(DoorstepAI.monitoredStops.map { self.serializeStop($0) })
353
+ }
354
+ }
355
+
356
+ private func parseDeliveryStop(_ raw: [String: Any]) -> DeliveryStop? {
357
+ guard let deliveryId = raw["deliveryId"] as? String,
358
+ let latitude = (raw["latitude"] as? NSNumber)?.doubleValue,
359
+ let longitude = (raw["longitude"] as? NSNumber)?.doubleValue else {
360
+ return nil
361
+ }
362
+ return DeliveryStop(
363
+ deliveryId: deliveryId,
364
+ address: (raw["address"] as? String) ?? "",
365
+ latitude: latitude,
366
+ longitude: longitude,
367
+ radiusMeters: (raw["radiusMeters"] as? NSNumber)?.doubleValue,
368
+ customerId: raw["customerId"] as? String,
369
+ driverId: raw["driverId"] as? String
370
+ )
371
+ }
372
+
373
+ private func parseRouteOptions(_ raw: NSDictionary?) -> RouteGeofenceOptions {
374
+ guard let o = raw as? [String: Any] else { return RouteGeofenceOptions() }
375
+ // iOS RouteGeofenceOptions has no `manualForeground` (OS owns background region
376
+ // monitoring); a JS-supplied manualForeground is intentionally ignored here.
377
+ return RouteGeofenceOptions(
378
+ defaultRadiusMeters: (o["defaultRadiusMeters"] as? NSNumber)?.doubleValue ?? 250,
379
+ timeoutSeconds: (o["timeoutSeconds"] as? NSNumber)?.doubleValue,
380
+ autoStopAfterDropoffSeconds: (o["autoStopAfterDropoffSeconds"] as? NSNumber)?.doubleValue
381
+ )
382
+ }
383
+
384
+ private func serializeStop(_ s: DeliveryStop) -> [String: Any] {
385
+ var m: [String: Any] = [
386
+ "deliveryId": s.deliveryId,
387
+ "address": s.address,
388
+ "latitude": s.latitude,
389
+ "longitude": s.longitude
390
+ ]
391
+ if let r = s.radiusMeters { m["radiusMeters"] = r }
392
+ if let c = s.customerId { m["customerId"] = c }
393
+ if let d = s.driverId { m["driverId"] = d }
394
+ return m
395
+ }
396
+
253
397
  private func parseCoordinates(_ raw: NSDictionary?) -> LatLngObject? {
254
398
  guard let coordinates = raw as? [String: Any],
255
399
  let lat = coordinates["lat"] as? Double,