react-native-move-sdk 0.1.5 → 0.2.1

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.
@@ -0,0 +1,607 @@
1
+ import CoreMotion
2
+ import Foundation
3
+ import DolphinMoveSDK
4
+
5
+ internal protocol MoveSDKLauncherDelegate: AnyObject {
6
+ func send(event: MoveSDKLauncher.Event, data: [String: Any])
7
+ }
8
+
9
+ typealias MoveSDKErrorListItem = [String:Any]
10
+
11
+ @objc(RCTMoveSdk)
12
+ class RCTMoveSdk: RCTEventEmitter {
13
+ private enum CError: String, Error {
14
+ case transformedServices = "Unknown driving service identifier in init parameters"
15
+ case transformedTimelineDetectionServices = "Unknown timeline service identifier in init parameters"
16
+ case transformedWalkingServices = "Unknown walking service identifier in init parameters"
17
+ case transformedOtherServices = "Unknown other service identifier in init parameters"
18
+ }
19
+
20
+ private enum ErrorCode: String {
21
+ case setupError = "SETUP_ERROR"
22
+ case resolveFailed = "RESOLVE_FAILED"
23
+ case serviceUnavailable = "SERVICE_UNAVAILABLE"
24
+ case thresholdReached = "THRESHOLD_REACHED"
25
+ case geocodeError = "GEOCODE_ERROR"
26
+ case authInvalid = "AUTH_INVALID"
27
+ case initializationError = "INITIALIZATION_ERROR"
28
+ case locationError = "LOCATION_ERROR"
29
+ case networkError = "NETWORK_ERROR"
30
+ case assistanceCallError = "ASSISTANCE_CALL_ERROR"
31
+ case configurationError = "CONFIGURATION_ERROR"
32
+
33
+ }
34
+
35
+ let motionManager = CMMotionActivityManager()
36
+
37
+ override func startObserving() {
38
+ MoveSDKLauncher.shared.delegate = self
39
+ }
40
+
41
+ @objc
42
+ func setup(_ contractId: String,
43
+ accessToken: String,
44
+ refreshToken: String,
45
+ productId: Int64,
46
+ timelineDetectionServices: Array<String>,
47
+ drivingServices: Array<String>,
48
+ walkingServices: Array<String>,
49
+ resolve: @escaping RCTPromiseResolveBlock,
50
+ reject: @escaping RCTPromiseRejectBlock) {
51
+
52
+ do {
53
+
54
+ let transformedServices: [MoveConfig.DrivingService] = try drivingServices.map {
55
+ switch $0 {
56
+ case "DISTRACTION_FREE_DRIVING":
57
+ return .distractionFreeDriving
58
+ case "DRIVING_BEHAVIOUR":
59
+ return .drivingBehavior
60
+ default:
61
+ throw CError.transformedServices
62
+ }
63
+ }
64
+
65
+ let transformedWalkingServices: [MoveConfig.WalkingService] = try walkingServices.map {
66
+ if $0 == "LOCATION" {
67
+ return .location
68
+ }
69
+ throw CError.transformedWalkingServices
70
+ }
71
+
72
+ let transformedDetectionServices: [MoveConfig.DetectionService] = try! timelineDetectionServices.map {
73
+ switch $0 {
74
+ case "DRIVING":
75
+ return .driving(transformedServices)
76
+ case "WALKING":
77
+ return .walking(transformedWalkingServices)
78
+ case "CYCLING":
79
+ return .cycling
80
+ case "PUBLIC_TRANSPORT":
81
+ return .publicTransport
82
+ case "ASSISTANCE_CALL":
83
+ return .assistanceCall
84
+ case "POINTS_OF_INTEREST":
85
+ return .pointsOfInterest
86
+ case "AUTOMATIC_IMPACT_DETECTION":
87
+ return .automaticImpactDetection
88
+ default:
89
+ throw CError.transformedTimelineDetectionServices
90
+ }
91
+ }
92
+
93
+ // TODO: Fix wrapper and add config option from RN for walking services (actually only 1 now)
94
+ let sdkConfig = MoveConfig(detectionService: transformedDetectionServices)
95
+ let auth = MoveAuth(userToken: accessToken, refreshToken: refreshToken, userID: contractId, projectID: productId)
96
+
97
+ DispatchQueue.main.async {
98
+ MoveSDKLauncher.shared.setup(auth: auth, config: sdkConfig)
99
+ }
100
+ resolve("OK")
101
+ } catch {
102
+ reject(ErrorCode.setupError.rawValue, "\(error)", nil)
103
+ }
104
+ }
105
+
106
+ @objc
107
+ func getState(
108
+ _ resolve: RCTPromiseResolveBlock,
109
+ rejecter reject: RCTPromiseRejectBlock
110
+ ) {
111
+ let value = "\(MoveSDK.shared.getSDKState())".uppercased()
112
+ resolve(value)
113
+ }
114
+
115
+ @objc
116
+ func getAuthState(
117
+ _ resolve: RCTPromiseResolveBlock,
118
+ rejecter reject: RCTPromiseRejectBlock
119
+ ) {
120
+ let value = "\(MoveSDK.shared.getAuthState())".uppercased()
121
+ resolve(value)
122
+ }
123
+
124
+ @objc
125
+ func setAssistanceMetaData(_ data: String) {
126
+
127
+ }
128
+
129
+ @objc
130
+ func geocode(_ latitude: Double, _ longitude: Double,
131
+ resolver resolve: @escaping RCTPromiseResolveBlock,
132
+ rejecter reject: @escaping RCTPromiseRejectBlock
133
+ ) {
134
+ MoveSDK.shared.geocode(latitude: latitude, longitude: longitude) { result in
135
+ switch result {
136
+ case let .success(value):
137
+ resolve(value)
138
+ case let .failure(err):
139
+ switch err {
140
+ case .resolveFailed:
141
+ reject(ErrorCode.resolveFailed.rawValue, nil, nil)
142
+ case .serviceUnreachable:
143
+ reject(ErrorCode.serviceUnavailable.rawValue, nil, nil)
144
+ case .thresholdReached:
145
+ reject(ErrorCode.thresholdReached.rawValue, nil, nil)
146
+ @unknown default:
147
+ reject(ErrorCode.geocodeError.rawValue, nil, nil)
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ @objc
154
+ func updateAuth(
155
+ _ contractId: String,
156
+ accessToken: String,
157
+ refreshToken: String,
158
+ productId: Int64,
159
+ resolver resolve: @escaping RCTPromiseResolveBlock,
160
+ rejecter reject: @escaping RCTPromiseRejectBlock
161
+ ) {
162
+ let auth = MoveAuth(userToken: accessToken, refreshToken: refreshToken, userID: contractId, projectID: productId)
163
+
164
+ MoveSDKLauncher.shared.sdk.update(auth: auth) { configurationError in
165
+ if let configurationError = configurationError {
166
+ switch configurationError {
167
+ case .authInvalid:
168
+ reject(ErrorCode.authInvalid.rawValue, nil, nil)
169
+ @unknown default:
170
+ reject(ErrorCode.configurationError.rawValue, nil, nil)
171
+ }
172
+ } else {
173
+ resolve("OK")
174
+ }
175
+ }
176
+ }
177
+
178
+ @objc
179
+ func getTripState(
180
+ _ resolve: RCTPromiseResolveBlock,
181
+ rejecter reject: RCTPromiseRejectBlock
182
+ ) {
183
+ let value = "\(MoveSDK.shared.getTripState())".uppercased()
184
+ resolve(value)
185
+ }
186
+
187
+ @objc
188
+ func getWarnings(
189
+ _ resolve: RCTPromiseResolveBlock,
190
+ rejecter reject: RCTPromiseRejectBlock
191
+ ) {
192
+ resolve(MoveSDKLauncher.shared.getServiceWarnings())
193
+ }
194
+
195
+ @objc
196
+ func getErrors(
197
+ _ resolve: RCTPromiseResolveBlock,
198
+ rejecter reject: RCTPromiseRejectBlock
199
+ ) {
200
+ resolve(MoveSDKLauncher.shared.getServiceFailures())
201
+ }
202
+
203
+ @objc
204
+ func startAutomaticDetection() {
205
+ MoveSDK.shared.startAutomaticDetection()
206
+ }
207
+
208
+ @objc
209
+ func stopAutomaticDetection() {
210
+ MoveSDK.shared.stopAutomaticDetection()
211
+ }
212
+
213
+ @objc
214
+ func shutdown() {
215
+ MoveSDKLauncher.shared.shutdown()
216
+ }
217
+
218
+ @objc
219
+ func forceTripRecognition() {
220
+ MoveSDKLauncher.shared.sdk.forceTripRecognition()
221
+ }
222
+
223
+ @objc
224
+ func synchronizeUserData(
225
+ _ resolve: @escaping RCTPromiseResolveBlock,
226
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
227
+ MoveSDKLauncher.shared.sdk.synchronizeUserData() { success in
228
+ resolve(success)
229
+ }
230
+ }
231
+
232
+ @objc
233
+ func initiateAssistanceCall(
234
+ _ resolve: @escaping(RCTPromiseResolveBlock),
235
+ rejecter reject: @escaping(RCTPromiseRejectBlock)
236
+ ) {
237
+ MoveSDKLauncher.shared.sdk.initiateAssistanceCall { status in
238
+ switch status {
239
+ case .success:
240
+ resolve(true)
241
+ case .initializationError:
242
+ reject(ErrorCode.initializationError.rawValue, nil, nil)
243
+ case .locationError:
244
+ reject(ErrorCode.locationError.rawValue, nil, nil)
245
+ case .networkError:
246
+ reject(ErrorCode.networkError.rawValue, nil, nil)
247
+ @unknown default:
248
+ reject(ErrorCode.assistanceCallError.rawValue, nil, nil)
249
+ }
250
+ }
251
+ }
252
+
253
+ @objc
254
+ func ignoreCurrentTrip() {
255
+ MoveSDKLauncher.shared.sdk.ignoreCurrentTrip()
256
+ }
257
+
258
+ @objc
259
+ func finishCurrentTrip() {
260
+ MoveSDKLauncher.shared.sdk.finishCurrentTrip()
261
+ }
262
+
263
+ @objc
264
+ func resolveError() {
265
+ MoveSDKLauncher.shared.sdk.resolveSDKStateError()
266
+ }
267
+
268
+ @objc
269
+ func sendAppEvent(_ eventInfo: NSString) {
270
+ send(event: .appEvent, data: ["eventInfo": "\(eventInfo)"])
271
+ }
272
+
273
+ @objc
274
+ func requestMotionPermission() {
275
+ motionManager.queryActivityStarting(from: Date(), to: Date(), to: OperationQueue.main) { _, error in
276
+ }
277
+ }
278
+
279
+ @objc public static func initIfPossible(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
280
+ MoveSDKLauncher.shared.initIfPossibleWith(launchOptions: launchOptions)
281
+ }
282
+
283
+ @objc public static func performBackgroundFetch(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
284
+ MoveSDKLauncher.shared.performBackgroundFetch(completionHandler)
285
+ }
286
+
287
+ override func supportedEvents() -> [String]! {
288
+ MoveSDKLauncher.Event.allCases.map { $0.rawValue }
289
+ }
290
+
291
+ static override func moduleName() -> String! {
292
+ return "MoveSdk"
293
+ }
294
+
295
+ static override func requiresMainQueueSetup() -> Bool {
296
+ // To let React Native know if your module needs to be initialized on the main thread, before any JavaScript code executes.
297
+ // Otherwise you will see a warning that in the future your module may be initialized on a background thread unless you explicitly opt out
298
+ return true
299
+ }
300
+ }
301
+
302
+ extension RCTMoveSdk: MoveSDKLauncherDelegate {
303
+ func send(event: MoveSDKLauncher.Event, data: [String: Any]) {
304
+ sendEvent(withName: event.rawValue, body: data)
305
+ }
306
+ }
307
+
308
+ internal class MoveSDKLauncher {
309
+ internal enum Event: String, CaseIterable {
310
+ case appEvent = "MOVE_SDK_APP_EVENT"
311
+ case sdkState = "MOVE_SDK_STATE"
312
+ case tripState = "MOVE_SDK_TRIP_STATE"
313
+ case authState = "MOVE_SDK_AUTH_STATE"
314
+ case warning = "MOVE_SDK_WARNINGS"
315
+ case failure = "MOVE_SDK_ERRORS"
316
+ }
317
+
318
+ private enum DefaultsKey: String {
319
+ case auth = "MOVE.WRAPPER.AUTH"
320
+ case config = "MOVE.WRAPPER.CONFIG"
321
+ }
322
+
323
+ weak var delegate: MoveSDKLauncherDelegate?
324
+
325
+ static let shared = MoveSDKLauncher()
326
+
327
+ let sdk = MoveSDK.shared
328
+ var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
329
+
330
+ init() {
331
+ sdk.setSDKStateListener { state in
332
+ let value = "\(state)".uppercased()
333
+ self.delegate?.send(event: .sdkState, data: ["state": "\(value)"])
334
+ }
335
+
336
+ sdk.setTripStateListener { state in
337
+ let value = "\(state)".uppercased()
338
+ self.delegate?.send(event: .tripState, data: ["state": "\(value)"])
339
+ }
340
+
341
+ sdk.setAuthStateUpdateListener { state in
342
+ let value = "\(state)".uppercased()
343
+ self.delegate?.send(event: .authState, data: ["state": "\(value)"])
344
+ }
345
+
346
+ sdk.setServiceFailureListener { failures in
347
+ let data: [String: Any] = ["errors": self.convert(failures: failures)]
348
+ self.delegate?.send(event: .failure, data: data)
349
+ }
350
+
351
+ sdk.setServiceWarningListener { warnings in
352
+ let data: [String: Any] = ["warnings": self.convert(warnings: warnings)]
353
+ self.delegate?.send(event: .warning, data: data)
354
+ }
355
+ }
356
+
357
+ func getServiceWarnings() -> [MoveSDKErrorListItem] {
358
+ return convert(warnings: sdk.getServiceWarnings())
359
+ }
360
+
361
+ func getServiceFailures() -> [MoveSDKErrorListItem] {
362
+ return convert(failures: sdk.getServiceFailures())
363
+ }
364
+
365
+ func convert(service: DolphinMoveSDK.MoveConfig.DetectionService) -> String {
366
+ return service.debugDescription
367
+ // switch service {
368
+ // case let .driving(sub):
369
+ // if sub.isEmpty {
370
+ // return "DRIVING"
371
+ // } else {
372
+ // return service.debugDescription
373
+ // }
374
+ // case .cycling:
375
+ // return "CYCLING"
376
+ // case let .walking(sub):
377
+ // if sub.isEmpty {
378
+ // return "WALKING"
379
+ // } else {
380
+ // return service.debugDescription
381
+ // }
382
+ // case .places:
383
+ // return "PLACES"
384
+ // case .publicTransport:
385
+ // return "PUBLIC_TRANSPORT"
386
+ // case .pointsOfInterest:
387
+ // return "POINTS_OF_INTEREST"
388
+ // case .automaticImpactDetection:
389
+ // return "AUTOMATIC_IMPACT_DETECTION"
390
+ // case .assistanceCall:
391
+ // return "ASSISTANCE_CALL"
392
+ // @unknown default:
393
+ // return service.debugDescription
394
+ // }
395
+ }
396
+
397
+ func convert(failures: [DolphinMoveSDK.MoveServiceFailure]) -> [MoveSDKErrorListItem] {
398
+ var converted: [MoveSDKErrorListItem] = []
399
+
400
+ for failure in failures {
401
+ var reasons: [String] = []
402
+ switch failure.reason {
403
+ case let .missingPermission(permissions):
404
+ let content: [String] = permissions.map { self.permissionString($0) }
405
+ reasons = content
406
+ case .unauthorized:
407
+ reasons = ["Unauthorized"]
408
+ @unknown default:
409
+ break
410
+ }
411
+ converted.append(["service": convert(service: failure.service), "reasons": reasons])
412
+ }
413
+
414
+ return converted
415
+ }
416
+
417
+ func convert(warnings: [DolphinMoveSDK.MoveServiceWarning]) -> [MoveSDKErrorListItem] {
418
+ var converted: [MoveSDKErrorListItem] = []
419
+
420
+ for warning in warnings {
421
+ var reasons: [String] = []
422
+ switch warning.reason {
423
+ case let .missingPermission(permissions):
424
+ let content: [String] = permissions.map { self.permissionString($0) }
425
+ reasons = content
426
+ @unknown default:
427
+ break
428
+ }
429
+ converted.append(["service": convert(service: warning.service), "reasons": reasons])
430
+ }
431
+
432
+ return converted
433
+ }
434
+
435
+ func permissionString(_ permission: DolphinMoveSDK.MovePermission) -> String {
436
+ switch permission {
437
+ case .location:
438
+ return "LOCATION_PERMISSION_MISSING"
439
+ case .backgroundLocation:
440
+ return "BACKGROUND_PERMISSION_MISSING"
441
+ case .preciseLocation:
442
+ return "PRECISE_LOCATION_PERMISSION_MISSING"
443
+ case .motionActivity:
444
+ return "MOTION_PERMISSION_MISSING"
445
+ case .gyroscope:
446
+ return "GYROSCOPE_BROKEN"
447
+ case .accelerometer:
448
+ return "ACCELEROMETER_BROKEN"
449
+ @unknown default:
450
+ return "UNKNOWN"
451
+ }
452
+ }
453
+
454
+ internal func initIfPossibleWith(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
455
+ self.launchOptions = launchOptions
456
+ MoveSDK.shared.initialize(launchOptions: launchOptions)
457
+
458
+ let oldConfig: OldConfig? = MoveSDKLauncher.decode(.config)
459
+ let oldAuth: OldAuth? = MoveSDKLauncher.decode(.auth)
460
+
461
+ /// migrate
462
+ if let oldAuth = oldAuth, let oldConfig = oldConfig {
463
+ let auth = DolphinMoveSDK.MoveAuth(userToken: oldAuth.userToken, refreshToken: oldAuth.refreshToken, userID: oldAuth.contractID, projectID: oldAuth.productID)
464
+
465
+ MoveSDK.shared.setup(auth: auth, config: oldConfig.convert())
466
+ MoveSDKLauncher.encode(nil as OldAuth?, forKey: .auth)
467
+ }
468
+
469
+ let isInRunningState = UserDefaults.standard.bool(forKey: "MOVE.WRAPPER.RUNNING")
470
+ if isInRunningState {
471
+ sdk.startAutomaticDetection()
472
+ UserDefaults.standard.removeObject(forKey: "MOVE.WRAPPER.RUNNING")
473
+ }
474
+ }
475
+
476
+ internal func performBackgroundFetch(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
477
+ MoveSDK.shared.performBackgroundFetch(completionHandler)
478
+ }
479
+
480
+ internal func setup(auth: DolphinMoveSDK.MoveAuth, config: DolphinMoveSDK.MoveConfig) {
481
+ MoveSDK.shared.setup(auth: auth, config: config)
482
+ }
483
+
484
+ func shutdown() {
485
+ sdk.shutDown()
486
+ }
487
+
488
+ private static func decode<T>(_ key: DefaultsKey) -> T? where T: Decodable {
489
+ let decoder = PropertyListDecoder()
490
+
491
+ if let data = UserDefaults.standard.object(forKey: key.rawValue) as? Data {
492
+ return try? decoder.decode(T.self, from: data)
493
+ }
494
+
495
+ return nil
496
+ }
497
+
498
+ private static func encode<T>(_ encodable: T?, forKey key: DefaultsKey) where T: Encodable {
499
+ if let encodable = encodable {
500
+ let encoder = PropertyListEncoder()
501
+ if let data = try? encoder.encode(encodable) {
502
+ UserDefaults.standard.set(data, forKey: key.rawValue)
503
+ }
504
+ } else {
505
+ UserDefaults.standard.removeObject(forKey: key.rawValue)
506
+ }
507
+ }
508
+ }
509
+
510
+ struct OldAuth: Codable {
511
+ var userToken: String
512
+ var refreshToken: String
513
+ var contractID: String
514
+ var productID: Int64
515
+ }
516
+
517
+ struct OldConfig: Codable {
518
+ /// Expresses modes of transport to be detected
519
+ enum TimelineDetectionService: Int, Codable {
520
+ /// Detect driving behaviour
521
+ case driving = 1
522
+
523
+ /// Detect cycling behaviour
524
+ case bicycle = 2
525
+
526
+ /// Detect walking behaviour
527
+ case walking = 3
528
+
529
+ /// Detect visits
530
+ case places = 4
531
+
532
+ case publicTransport = 5
533
+ }
534
+
535
+ /// Expresses driving services to be detected
536
+ enum DrivingService: Int, Codable {
537
+ /// Detect distraction free driving
538
+ case dfd = 101
539
+
540
+ /// Detect driving behaviour
541
+ case behaviour = 102
542
+ }
543
+
544
+ /// Expresses walking services to be detected
545
+ enum WalkingService: Int, Codable {
546
+ /// Detects GPS location for collected walking events
547
+ case location = 301
548
+ }
549
+
550
+ /// Expresses other services to be detected
551
+ enum OtherService: Int, Codable {
552
+ /// Monitor points of intrest
553
+ case poi = 401
554
+ case impact = 402
555
+ }
556
+
557
+ var timelineDetectionService: [TimelineDetectionService]
558
+ var drivingServices: [DrivingService]
559
+ var walkingServices: [WalkingService]
560
+ var otherServices: [OtherService]
561
+ var tripStatusUpdateFrequency: Double = -1
562
+
563
+ func convert() -> DolphinMoveSDK.MoveConfig {
564
+ let driving: [DolphinMoveSDK.MoveConfig.DrivingService] = drivingServices.map {
565
+ switch $0 {
566
+ case .behaviour:
567
+ return .drivingBehavior
568
+ case .dfd:
569
+ return .distractionFreeDriving
570
+ }
571
+ }
572
+
573
+ let walking: [DolphinMoveSDK.MoveConfig.WalkingService] = walkingServices.map {
574
+ switch $0 {
575
+ case .location:
576
+ return .location
577
+ }
578
+ }
579
+
580
+ let services: [DolphinMoveSDK.MoveConfig.DetectionService] = timelineDetectionService.map {
581
+ switch $0 {
582
+
583
+ case .driving:
584
+ return .driving(driving)
585
+ case .bicycle:
586
+ return .cycling
587
+ case .walking:
588
+ return .walking(walking)
589
+ case .places:
590
+ return .places
591
+ case .publicTransport:
592
+ return .publicTransport
593
+ }
594
+ }
595
+
596
+ let otherServices: [DolphinMoveSDK.MoveConfig.DetectionService] = otherServices.map {
597
+ switch $0 {
598
+ case .poi:
599
+ return .pointsOfInterest
600
+ case .impact:
601
+ return .automaticImpactDetection
602
+ }
603
+ }
604
+
605
+ return DolphinMoveSDK.MoveConfig(detectionService: services + otherServices)
606
+ }
607
+ }