leeo-react-native-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LeeoSdk.podspec +24 -0
- package/README.md +34 -0
- package/android/build.gradle +20 -0
- package/android/src/main/java/com/leeo/sdk/LeeoApi.kt +105 -0
- package/android/src/main/java/com/leeo/sdk/LeeoDriveKitBridge.kt +122 -0
- package/android/src/main/java/com/leeo/sdk/LeeoSdkModule.kt +182 -0
- package/android/src/main/java/com/leeo/sdk/LeeoSdkPackage.kt +16 -0
- package/ios/LeeoSdk-Bridging-Header.h +1 -0
- package/ios/LeeoSdk.mm +44 -0
- package/ios/LeeoSdk.swift +169 -0
- package/lib/typescript/index.d.ts +47 -0
- package/lib/typescript/index.js +53 -0
- package/package.json +35 -0
- package/src/index.ts +88 -0
package/LeeoSdk.podspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
pkg = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
3
|
+
|
|
4
|
+
Pod::Spec.new do |s|
|
|
5
|
+
s.name = "LeeoSdk"
|
|
6
|
+
s.version = pkg["version"]
|
|
7
|
+
s.summary = pkg["description"]
|
|
8
|
+
s.homepage = pkg["repository"]["url"] rescue ""
|
|
9
|
+
s.license = pkg["license"] rescue "UNLICENSED"
|
|
10
|
+
s.author = pkg["author"] rescue ""
|
|
11
|
+
s.source = { :git => "", :tag => "#{s.version}" }
|
|
12
|
+
s.platforms = { :ios => "13.0" }
|
|
13
|
+
s.source_files = "ios/*.{m,mm,swift,h}"
|
|
14
|
+
s.pod_target_xcconfig = {
|
|
15
|
+
"DEFINES_MODULE" => "YES",
|
|
16
|
+
"SWIFT_OBJC_BRIDGING_HEADER" => "$(PODS_TARGET_SRCROOT)/ios/LeeoSdk-Bridging-Header.h"
|
|
17
|
+
}
|
|
18
|
+
s.requires_arc = true
|
|
19
|
+
s.dependency "React-Core"
|
|
20
|
+
# Leeo iOS SDK (same code as SPM). Install with: npm install leeo-ios-sdk (or add to your app).
|
|
21
|
+
leeo_ios_path = File.directory?(File.join(__dir__, "node_modules/leeo-ios-sdk")) ? "node_modules/leeo-ios-sdk" : "../leeo-ios-sdk"
|
|
22
|
+
s.dependency "LeeoSDK", :path => leeo_ios_path
|
|
23
|
+
s.swift_version = "5.0"
|
|
24
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Leeo React Native SDK
|
|
2
|
+
|
|
3
|
+
React Native bridge for Leeo trip tracking and incident reporting. Same flow as the native [Android](../leeo-android-sdk) and [iOS](../leeo-ios-sdk) SDKs: setup (fleet-service + DriveKit), manual trip (startTrip/stopTrip), shift (startShift/stopShift), incident reporting.
|
|
4
|
+
|
|
5
|
+
- **Test the demo app:** Create a new RN app, add this package (and `leeo-ios-sdk` for iOS), link Android/iOS, copy [example/App.tsx](example/App.tsx), then run. Full steps in [INSTALL_AND_TEST.md](INSTALL_AND_TEST.md).
|
|
6
|
+
- **Clients:** Install via npm or Git, then same link + permissions steps; see [INSTALL_AND_TEST.md](INSTALL_AND_TEST.md) for install and API usage.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
- **Android:** Add `LeeoSdkPackage` to your app. Configure fleet-service base URL (or omit for production). DriveKit is initialized automatically when `setup()` succeeds with a DriveQuant key.
|
|
11
|
+
- **iOS:** Same code as the native [Leeo iOS SDK](https://github.com/leeoinsurance/leeo-ios-sdk): the RN module is a thin bridge that calls the iOS SDK. Install the iOS SDK so the pod can resolve: `npm install leeo-ios-sdk` (or add it as a dependency; the RN package lists it as optional). Run `pod install` in `ios/`. The iOS SDK is built as a CocoaPods pod (same sources as SPM) and depends on `DriveKitTripAnalysis`.
|
|
12
|
+
|
|
13
|
+
## Android permissions (required for trip recording)
|
|
14
|
+
|
|
15
|
+
Your app must declare and request these at runtime so DriveKit can record trips and run in the background:
|
|
16
|
+
|
|
17
|
+
1. **AndroidManifest.xml** (in your app’s `android/app/src/main/`):
|
|
18
|
+
- `android.permission.ACCESS_FINE_LOCATION`
|
|
19
|
+
- `android.permission.ACCESS_COARSE_LOCATION`
|
|
20
|
+
- `android.permission.POST_NOTIFICATIONS` (Android 13+)
|
|
21
|
+
- `android.permission.ACTIVITY_RECOGNITION` (Android 10+; “Physical activity”)
|
|
22
|
+
|
|
23
|
+
2. **Runtime requests** (before calling Setup or Start Trip): request **POST_NOTIFICATIONS** on Android 13+ and **ACTIVITY_RECOGNITION** on Android 10+, e.g. in your root component or MainActivity using `PermissionsAndroid` or a library like `react-native-permissions`. If the user denies notifications or physical activity, trip recording may not work. See the [example README](example/README.md) for a minimal setup.
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
- **Config:** `baseUrl` is optional; defaults to `DEFAULT_BASE_URL` (`https://api.leeoinsurance.com`). Import `DEFAULT_BASE_URL` from the package.
|
|
28
|
+
- `Leeo.setup(config, tripNotification)` – POST fleet-service /fleet/v1/sdk/setup; on success configures DriveKit when telematics_provider returns a DriveQuant key. Same logic as Android/iOS (with 8s connection timeout if DriveKit doesn’t connect).
|
|
29
|
+
- `Leeo.startTrip(trackingId)` / `Leeo.stopTrip()` – manual trip (DriveKit).
|
|
30
|
+
- `Leeo.startShift(shiftId?)` / `Leeo.stopShift()` – shift/auto trip (DriveKit).
|
|
31
|
+
- `Leeo.teardown()` / `Leeo.wipeOut()` – clear state and tear down DriveKit.
|
|
32
|
+
- `Leeo.openIncidentReportingWebPage()` / `Leeo.getIncidentReportingURL()`
|
|
33
|
+
|
|
34
|
+
See [LEEO_SDK_DESIGN.md](../LEEO_SDK_DESIGN.md).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
android {
|
|
5
|
+
namespace "com.leeo.sdk"
|
|
6
|
+
compileSdk 34
|
|
7
|
+
defaultConfig {
|
|
8
|
+
minSdk 26
|
|
9
|
+
targetSdk 34
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dependencies {
|
|
14
|
+
implementation "com.facebook.react:react-native:+"
|
|
15
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.25"
|
|
16
|
+
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
|
17
|
+
implementation "com.google.code.gson:gson:2.10.1"
|
|
18
|
+
implementation "com.drivequant.drivekit:drivekit-core:2.26.0"
|
|
19
|
+
implementation "com.drivequant.drivekit:drivekit-trip-analysis:2.26.0"
|
|
20
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package com.leeo.sdk
|
|
2
|
+
|
|
3
|
+
import com.google.gson.Gson
|
|
4
|
+
import com.google.gson.annotations.SerializedName
|
|
5
|
+
import okhttp3.MediaType.Companion.toMediaType
|
|
6
|
+
import okhttp3.OkHttpClient
|
|
7
|
+
import okhttp3.Request
|
|
8
|
+
import okhttp3.RequestBody.Companion.toRequestBody
|
|
9
|
+
import java.util.concurrent.TimeUnit
|
|
10
|
+
|
|
11
|
+
internal object LeeoApi {
|
|
12
|
+
private const val SETUP_PATH = "/fleet/v1/sdk/setup"
|
|
13
|
+
private const val INCIDENT_AUTH_PATH = "/fleet/v1/sdk/incident-auth-url"
|
|
14
|
+
private const val HEADER_DRIVER_TOKEN = "x-driver-token"
|
|
15
|
+
private val gson = Gson()
|
|
16
|
+
private val jsonMediaType = "application/json; charset=utf-8".toMediaType()
|
|
17
|
+
private val client = OkHttpClient.Builder()
|
|
18
|
+
.connectTimeout(30, TimeUnit.SECONDS)
|
|
19
|
+
.readTimeout(30, TimeUnit.SECONDS)
|
|
20
|
+
.build()
|
|
21
|
+
|
|
22
|
+
fun postSetup(baseUrl: String, sdkKey: String, driverId: String, firstName: String, lastName: String, email: String, phoneNumber: String): Pair<Int, String?> {
|
|
23
|
+
val url = baseUrl.trimEnd('/') + SETUP_PATH
|
|
24
|
+
val body = mapOf(
|
|
25
|
+
"sdk_key" to sdkKey,
|
|
26
|
+
"driver_id" to driverId,
|
|
27
|
+
"first_name" to firstName,
|
|
28
|
+
"last_name" to lastName,
|
|
29
|
+
"email" to email,
|
|
30
|
+
"phone_number" to phoneNumber,
|
|
31
|
+
"sdk_family" to "leeo"
|
|
32
|
+
)
|
|
33
|
+
val request = Request.Builder()
|
|
34
|
+
.url(url)
|
|
35
|
+
.post(gson.toJson(body).toRequestBody(jsonMediaType))
|
|
36
|
+
.addHeader("Content-Type", "application/json")
|
|
37
|
+
.build()
|
|
38
|
+
return runCatching {
|
|
39
|
+
val response = client.newCall(request).execute()
|
|
40
|
+
response.code to response.body?.string()
|
|
41
|
+
}.getOrElse { -1 to null }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun getIncidentAuthUrl(baseUrl: String, loginToken: String, platform: String = "android"): Pair<Int, String?> {
|
|
45
|
+
val url = baseUrl.trimEnd('/') + INCIDENT_AUTH_PATH + "?platform=$platform&source=fm_sdk&version=1"
|
|
46
|
+
val request = Request.Builder()
|
|
47
|
+
.url(url)
|
|
48
|
+
.get()
|
|
49
|
+
.addHeader(HEADER_DRIVER_TOKEN, loginToken)
|
|
50
|
+
.build()
|
|
51
|
+
return runCatching {
|
|
52
|
+
val response = client.newCall(request).execute()
|
|
53
|
+
val bodyStr = response.body?.string() ?: return@runCatching response.code to null
|
|
54
|
+
if (!response.isSuccessful) return@runCatching response.code to null
|
|
55
|
+
val json = gson.fromJson(bodyStr, IncidentAuthResponse::class.java)
|
|
56
|
+
val urlFromData = (json?.data as? Map<*, *>)?.get("claims_incident_auth_url") as? String
|
|
57
|
+
response.code to urlFromData
|
|
58
|
+
}.getOrElse { -1 to null }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private class IncidentAuthResponse(val data: Any?)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
internal object LeeoSetupState {
|
|
65
|
+
@Volatile var loginToken: String? = null
|
|
66
|
+
@Volatile var lastBaseUrl: String? = null
|
|
67
|
+
@Volatile var driveQuantSdkKey: String? = null
|
|
68
|
+
@Volatile var isSetup: Boolean = false
|
|
69
|
+
|
|
70
|
+
fun setFromResponse(json: String, baseUrl: String): Boolean {
|
|
71
|
+
return try {
|
|
72
|
+
// Backend returns { "success": true, "data": { "user_info", "telematics_provider", ... } }
|
|
73
|
+
val response = gson.fromJson(json, SetupResponse::class.java)
|
|
74
|
+
val data = response.data
|
|
75
|
+
if (response.success == true && data?.userInfo?.loginToken != null) {
|
|
76
|
+
loginToken = data.userInfo.loginToken
|
|
77
|
+
lastBaseUrl = baseUrl
|
|
78
|
+
driveQuantSdkKey = data.telematicsProvider?.driveQuant?.sdkKey
|
|
79
|
+
isSetup = true
|
|
80
|
+
true
|
|
81
|
+
} else false
|
|
82
|
+
} catch (_: Exception) {
|
|
83
|
+
false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fun clear() {
|
|
88
|
+
loginToken = null
|
|
89
|
+
lastBaseUrl = null
|
|
90
|
+
driveQuantSdkKey = null
|
|
91
|
+
isSetup = false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private data class SetupResponse(
|
|
95
|
+
val success: Boolean?,
|
|
96
|
+
val data: SetupData?
|
|
97
|
+
)
|
|
98
|
+
private data class SetupData(
|
|
99
|
+
@SerializedName("user_info") val userInfo: UserInfo?,
|
|
100
|
+
@SerializedName("telematics_provider") val telematicsProvider: TelematicsProvider?
|
|
101
|
+
)
|
|
102
|
+
private data class UserInfo(@SerializedName("login_token") val loginToken: String?)
|
|
103
|
+
private data class TelematicsProvider(@SerializedName("drive_quant") val driveQuant: DriveQuant?)
|
|
104
|
+
private data class DriveQuant(@SerializedName("sdk_key") val sdkKey: String?)
|
|
105
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package com.leeo.sdk
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import com.drivequant.drivekit.core.DriveKit
|
|
8
|
+
import com.drivequant.drivekit.core.driver.UpdateUserIdStatus
|
|
9
|
+
import com.drivequant.drivekit.core.driver.deletion.DeleteAccountStatus
|
|
10
|
+
import com.drivequant.drivekit.core.networking.*
|
|
11
|
+
import com.drivequant.drivekit.tripanalysis.DriveKitTripAnalysis
|
|
12
|
+
import com.drivequant.drivekit.tripanalysis.entity.TripNotification
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bridges Leeo React Native module to DriveKit. Call [ensureInitialized] before [onSetupSuccess].
|
|
16
|
+
*/
|
|
17
|
+
internal object LeeoDriveKitBridge : DriveKitListener {
|
|
18
|
+
|
|
19
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
20
|
+
|
|
21
|
+
/** Initialize DriveKit from app context. Call from setup before configuring key. Returns true if initialized. */
|
|
22
|
+
fun ensureInitialized(context: Context): Boolean {
|
|
23
|
+
val app = context.applicationContext as? Application ?: return false
|
|
24
|
+
if (!DriveKit.isInitialized()) {
|
|
25
|
+
DriveKit.initialize(app)
|
|
26
|
+
DriveKitTripAnalysis.initialize(TripNotification("Trip", "Recording...", 0))
|
|
27
|
+
}
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Volatile
|
|
32
|
+
private var pendingSetupCallback: ((Boolean, String?) -> Unit)? = null
|
|
33
|
+
|
|
34
|
+
private var setupTimeoutRunnable: Runnable? = null
|
|
35
|
+
|
|
36
|
+
/** Timeout (ms) after which we call callback(true) if DriveKit hasn't connected (e.g. in emulator). */
|
|
37
|
+
private const val SETUP_CONNECTION_TIMEOUT_MS = 8_000L
|
|
38
|
+
|
|
39
|
+
fun onSetupSuccess(
|
|
40
|
+
driveQuantSdkKey: String,
|
|
41
|
+
driverId: String,
|
|
42
|
+
callback: (Boolean, String?) -> Unit,
|
|
43
|
+
) {
|
|
44
|
+
if (!DriveKit.isInitialized()) {
|
|
45
|
+
mainHandler.post { callback(false, "DriveKit not initialized. Call LeeoSdk.initialize() at app startup.") }
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
pendingSetupCallback = callback
|
|
49
|
+
DriveKit.addDriveKitListener(this)
|
|
50
|
+
DriveKit.setApiKey(driveQuantSdkKey)
|
|
51
|
+
DriveKitTripAnalysis.setStopTimeOut(Int.MAX_VALUE)
|
|
52
|
+
DriveKit.setUserId(driverId)
|
|
53
|
+
val timeoutRunnable = Runnable {
|
|
54
|
+
val cb = pendingSetupCallback
|
|
55
|
+
if (cb != null) {
|
|
56
|
+
pendingSetupCallback = null
|
|
57
|
+
setupTimeoutRunnable = null
|
|
58
|
+
DriveKit.removeDriveKitListener(this)
|
|
59
|
+
cb(true, null)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
setupTimeoutRunnable = timeoutRunnable
|
|
63
|
+
mainHandler.postDelayed(timeoutRunnable, SETUP_CONNECTION_TIMEOUT_MS)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun onConnected() {
|
|
67
|
+
mainHandler.post {
|
|
68
|
+
setupTimeoutRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
69
|
+
setupTimeoutRunnable = null
|
|
70
|
+
DriveKit.removeDriveKitListener(this)
|
|
71
|
+
pendingSetupCallback?.invoke(true, null)
|
|
72
|
+
pendingSetupCallback = null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override fun onDisconnected() {}
|
|
77
|
+
|
|
78
|
+
override fun onAuthenticationError(errorType: RequestError) {}
|
|
79
|
+
|
|
80
|
+
override fun userIdUpdateStatus(status: UpdateUserIdStatus, userId: String?) {}
|
|
81
|
+
|
|
82
|
+
override fun onAccountDeleted(status: DeleteAccountStatus) {}
|
|
83
|
+
|
|
84
|
+
fun tearDown(callback: () -> Unit) {
|
|
85
|
+
if (!DriveKit.isInitialized()) {
|
|
86
|
+
mainHandler.post(callback)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
if (DriveKitTripAnalysis.isTripRunning()) {
|
|
90
|
+
DriveKitTripAnalysis.stopTrip()
|
|
91
|
+
DriveKitTripAnalysis.activateAutoStart(false)
|
|
92
|
+
}
|
|
93
|
+
DriveKit.reset()
|
|
94
|
+
mainHandler.post(callback)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun startTrip(trackingId: String): Boolean {
|
|
98
|
+
if (!DriveKit.isInitialized() || !DriveKit.isUserConnected()) return false
|
|
99
|
+
DriveKitTripAnalysis.updateTripMetaData("tracking_id", trackingId)
|
|
100
|
+
DriveKitTripAnalysis.startTrip()
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fun stopTrip(): Boolean {
|
|
105
|
+
if (!DriveKit.isInitialized()) return false
|
|
106
|
+
DriveKitTripAnalysis.stopTrip()
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fun startShift(): Boolean {
|
|
111
|
+
if (!DriveKit.isInitialized() || !DriveKit.isUserConnected()) return false
|
|
112
|
+
DriveKitTripAnalysis.activateAutoStart(true)
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun stopShift(): Boolean {
|
|
117
|
+
if (!DriveKit.isInitialized()) return false
|
|
118
|
+
if (DriveKitTripAnalysis.isTripRunning()) DriveKitTripAnalysis.stopTrip()
|
|
119
|
+
DriveKitTripAnalysis.activateAutoStart(false)
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
package com.leeo.sdk
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import com.facebook.react.bridge.*
|
|
6
|
+
import java.util.concurrent.Executors
|
|
7
|
+
|
|
8
|
+
class LeeoSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
|
|
10
|
+
private val executor = Executors.newSingleThreadExecutor()
|
|
11
|
+
|
|
12
|
+
override fun getName(): String = "LeeoSdk"
|
|
13
|
+
|
|
14
|
+
@ReactMethod
|
|
15
|
+
fun setup(config: ReadableMap, tripNotification: ReadableMap, promise: Promise) {
|
|
16
|
+
val baseUrl = config.getString("baseUrl") ?: run {
|
|
17
|
+
promise.resolve(errorMap("INVALID_DRIVER_ID", "baseUrl is required"))
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
val sdkKey = config.getString("sdkKey") ?: ""
|
|
21
|
+
val driverId = config.getString("driverId") ?: ""
|
|
22
|
+
val attrs = config.getMap("driverAttributes") ?: run {
|
|
23
|
+
promise.resolve(errorMap("INVALID_DRIVER_ID", "driverAttributes required"))
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
val firstName = (attrs.getString("firstName") ?: "").trim()
|
|
27
|
+
val lastName = (attrs.getString("lastName") ?: "").trim()
|
|
28
|
+
if (firstName.isBlank() || lastName.isBlank()) {
|
|
29
|
+
promise.resolve(errorMap("INVALID_DRIVER_ID", "first_name and last_name required"))
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
val email = attrs.getString("email") ?: ""
|
|
33
|
+
val phoneNumber = attrs.getString("phoneNumber") ?: ""
|
|
34
|
+
executor.execute {
|
|
35
|
+
val (code, body) = LeeoApi.postSetup(baseUrl, sdkKey, driverId, firstName, lastName, email, phoneNumber)
|
|
36
|
+
runOnReactQueue {
|
|
37
|
+
when {
|
|
38
|
+
code in 200..299 && body != null && LeeoSetupState.setFromResponse(body, baseUrl) -> {
|
|
39
|
+
val dqKey = LeeoSetupState.driveQuantSdkKey
|
|
40
|
+
if (!dqKey.isNullOrBlank()) {
|
|
41
|
+
if (!LeeoDriveKitBridge.ensureInitialized(reactApplicationContext)) {
|
|
42
|
+
promise.resolve(errorMap("LEEO_SDK_ERROR", "DriveKit could not be initialized"))
|
|
43
|
+
return@runOnReactQueue
|
|
44
|
+
}
|
|
45
|
+
LeeoDriveKitBridge.onSetupSuccess(dqKey, driverId) { success, errorMsg ->
|
|
46
|
+
runOnReactQueue {
|
|
47
|
+
if (success) promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
48
|
+
else promise.resolve(errorMap("LEEO_SDK_ERROR", errorMsg ?: "DriveKit setup failed"))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
code == 404 -> {
|
|
56
|
+
val msg = body ?: "Not found"
|
|
57
|
+
val isDriverNotFound = msg.contains("Driver", ignoreCase = true) && msg.contains("not found", ignoreCase = true)
|
|
58
|
+
val codeStr = if (isDriverNotFound) "DRIVER_NOT_FOUND" else "INVALID_OR_NOT_FOUND_SDK_KEY"
|
|
59
|
+
promise.resolve(errorMap(codeStr, msg))
|
|
60
|
+
}
|
|
61
|
+
code == 400 -> promise.resolve(errorMap("INVALID_DRIVER_ID", body ?: "Bad request"))
|
|
62
|
+
else -> promise.resolve(errorMap("NETWORK_NOT_AVAILABLE", body ?: "Setup failed (code $code)"))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun errorMap(code: String, message: String): WritableMap =
|
|
69
|
+
Arguments.createMap().apply { putBoolean("success", false); putString("errorCode", code); putString("message", message) }
|
|
70
|
+
|
|
71
|
+
private fun runOnReactQueue(r: () -> Unit) {
|
|
72
|
+
reactApplicationContext.runOnUiQueueThread(r)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@ReactMethod
|
|
76
|
+
fun teardown(promise: Promise) {
|
|
77
|
+
LeeoDriveKitBridge.tearDown {
|
|
78
|
+
runOnReactQueue {
|
|
79
|
+
LeeoSetupState.clear()
|
|
80
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@ReactMethod
|
|
86
|
+
fun startTrip(trackingId: String, promise: Promise) {
|
|
87
|
+
if (!LeeoSetupState.isSetup) {
|
|
88
|
+
promise.resolve(errorMap("SDK_NOT_SETUP", "Call setup first"))
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
val ok = LeeoDriveKitBridge.startTrip(trackingId)
|
|
92
|
+
if (ok) promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
93
|
+
else promise.resolve(errorMap("LEEO_SDK_ERROR", "DriveKit not ready"))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@ReactMethod
|
|
97
|
+
fun stopTrip(promise: Promise) {
|
|
98
|
+
val ok = LeeoDriveKitBridge.stopTrip()
|
|
99
|
+
promise.resolve(if (ok) Arguments.createMap().apply { putBoolean("success", true) } else errorMap("LEEO_SDK_ERROR", "DriveKit not initialized"))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@ReactMethod
|
|
103
|
+
fun startShift(shiftId: String?, promise: Promise) {
|
|
104
|
+
if (!LeeoSetupState.isSetup) {
|
|
105
|
+
promise.resolve(errorMap("SDK_NOT_SETUP", "Call setup first"))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
val ok = LeeoDriveKitBridge.startShift()
|
|
109
|
+
if (ok) promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
110
|
+
else promise.resolve(errorMap("LEEO_SDK_ERROR", "DriveKit not ready"))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@ReactMethod
|
|
114
|
+
fun stopShift(promise: Promise) {
|
|
115
|
+
val ok = LeeoDriveKitBridge.stopShift()
|
|
116
|
+
promise.resolve(if (ok) Arguments.createMap().apply { putBoolean("success", true) } else errorMap("LEEO_SDK_ERROR", "DriveKit not initialized"))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@ReactMethod
|
|
120
|
+
fun wipeOut(promise: Promise) {
|
|
121
|
+
LeeoDriveKitBridge.tearDown {
|
|
122
|
+
runOnReactQueue {
|
|
123
|
+
LeeoSetupState.clear()
|
|
124
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@ReactMethod
|
|
130
|
+
fun getBuildVersion(promise: Promise) {
|
|
131
|
+
promise.resolve("0.1.0")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@ReactMethod
|
|
135
|
+
fun openIncidentReportingWebPage(promise: Promise) {
|
|
136
|
+
val loginToken = LeeoSetupState.loginToken
|
|
137
|
+
val baseUrl = LeeoSetupState.lastBaseUrl
|
|
138
|
+
if (loginToken == null || baseUrl == null) {
|
|
139
|
+
promise.resolve(errorMap("SDK_NOT_SETUP", "Call setup first"))
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
executor.execute {
|
|
143
|
+
val (code, url) = LeeoApi.getIncidentAuthUrl(baseUrl, loginToken)
|
|
144
|
+
runOnReactQueue {
|
|
145
|
+
when {
|
|
146
|
+
code == 200 && !url.isNullOrBlank() -> {
|
|
147
|
+
try {
|
|
148
|
+
currentActivity?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
|
149
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
150
|
+
} catch (e: Exception) {
|
|
151
|
+
promise.resolve(errorMap("LEEO_SDK_ERROR", e.message ?: "Could not open browser"))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
code == 404 -> promise.resolve(errorMap("INVALID_OR_NOT_FOUND_SDK_KEY", "Driver not found"))
|
|
155
|
+
else -> promise.resolve(errorMap("NETWORK_NOT_AVAILABLE", "Failed to get incident URL"))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@ReactMethod
|
|
162
|
+
fun getIncidentReportingURL(promise: Promise) {
|
|
163
|
+
val loginToken = LeeoSetupState.loginToken
|
|
164
|
+
val baseUrl = LeeoSetupState.lastBaseUrl
|
|
165
|
+
if (loginToken == null || baseUrl == null) {
|
|
166
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", false); putString("errorCode", "SDK_NOT_SETUP") })
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
executor.execute {
|
|
170
|
+
val (code, url) = LeeoApi.getIncidentAuthUrl(baseUrl, loginToken)
|
|
171
|
+
runOnReactQueue {
|
|
172
|
+
if (code == 200 && url != null) {
|
|
173
|
+
promise.resolve(url)
|
|
174
|
+
} else if (code == 404) {
|
|
175
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", false); putString("errorCode", "INVALID_OR_NOT_FOUND_SDK_KEY") })
|
|
176
|
+
} else {
|
|
177
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", false); putString("errorCode", "NETWORK_NOT_AVAILABLE") })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.leeo.sdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class LeeoSdkPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(LeeoSdkModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
package/ios/LeeoSdk.mm
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(LeeoSdk, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(setup:(NSDictionary *)config
|
|
6
|
+
tripNotification:(NSDictionary *)tripNotification
|
|
7
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
8
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
+
|
|
10
|
+
RCT_EXTERN_METHOD(teardown:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
RCT_EXTERN_METHOD(startTrip:(NSString *)trackingId
|
|
14
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
RCT_EXTERN_METHOD(stopTrip:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
|
|
20
|
+
RCT_EXTERN_METHOD(startShift:(NSString *)shiftId
|
|
21
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
|
|
24
|
+
RCT_EXTERN_METHOD(stopShift:(RCTPromiseResolveBlock)resolve
|
|
25
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
26
|
+
|
|
27
|
+
RCT_EXTERN_METHOD(wipeOut:(RCTPromiseResolveBlock)resolve
|
|
28
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
29
|
+
|
|
30
|
+
RCT_EXTERN_METHOD(getBuildVersion:(RCTPromiseResolveBlock)resolve
|
|
31
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
32
|
+
|
|
33
|
+
RCT_EXTERN_METHOD(openIncidentReportingWebPage:(RCTPromiseResolveBlock)resolve
|
|
34
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
35
|
+
|
|
36
|
+
RCT_EXTERN_METHOD(getIncidentReportingURL:(RCTPromiseResolveBlock)resolve
|
|
37
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
38
|
+
|
|
39
|
+
+ (BOOL)requiresMainQueueSetup
|
|
40
|
+
{
|
|
41
|
+
return NO;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Leeo React Native SDK – iOS thin bridge to LeeoSDK (same as native iOS SDK).
|
|
3
|
+
// Depends on LeeoSDK pod (CocoaPods build of leeo-ios-sdk).
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import Foundation
|
|
7
|
+
import UIKit
|
|
8
|
+
import LeeoSDK
|
|
9
|
+
|
|
10
|
+
@objc(LeeoSdk)
|
|
11
|
+
class LeeoSdk: NSObject {
|
|
12
|
+
|
|
13
|
+
@objc static func requiresMainQueueSetup() -> Bool { false }
|
|
14
|
+
|
|
15
|
+
private static func success() -> [String: Any] { ["success": true] }
|
|
16
|
+
private static func error(_ code: String, _ message: String) -> [String: Any] {
|
|
17
|
+
["success": false, "errorCode": code, "message": message]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static func config(from configDict: NSDictionary, tripNotificationDict: NSDictionary) -> (LeeoConfiguration, LeeoTripNotification)? {
|
|
21
|
+
guard let baseUrl = configDict["baseUrl"] as? String, !baseUrl.isEmpty,
|
|
22
|
+
let sdkKey = configDict["sdkKey"] as? String,
|
|
23
|
+
let driverId = configDict["driverId"] as? String,
|
|
24
|
+
let attrs = configDict["driverAttributes"] as? NSDictionary,
|
|
25
|
+
let firstName = (attrs["firstName"] as? String)?.trimmingCharacters(in: .whitespaces),
|
|
26
|
+
let lastName = (attrs["lastName"] as? String)?.trimmingCharacters(in: .whitespaces),
|
|
27
|
+
!firstName.isEmpty, !lastName.isEmpty else { return nil }
|
|
28
|
+
let email = attrs["email"] as? String ?? ""
|
|
29
|
+
let phoneNumber = attrs["phoneNumber"] as? String ?? ""
|
|
30
|
+
let configuration = LeeoConfiguration(
|
|
31
|
+
baseUrl: baseUrl,
|
|
32
|
+
sdkKey: sdkKey,
|
|
33
|
+
driverId: driverId,
|
|
34
|
+
driverAttributes: LeeoDriverAttributes(
|
|
35
|
+
firstName: firstName,
|
|
36
|
+
lastName: lastName,
|
|
37
|
+
email: email,
|
|
38
|
+
phoneNumber: phoneNumber
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
let title = (tripNotificationDict["title"] as? String) ?? "Trip"
|
|
42
|
+
let content = (tripNotificationDict["content"] as? String) ?? "Recording..."
|
|
43
|
+
let iconId = (tripNotificationDict["iconId"] as? NSNumber)?.intValue ?? 0
|
|
44
|
+
let notification = LeeoTripNotification(title: title, content: content, iconId: iconId)
|
|
45
|
+
return (configuration, notification)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static func errorCode(from error: Error?) -> (String, String) {
|
|
49
|
+
guard let e = error else { return ("LEEO_SDK_ERROR", "Unknown error") }
|
|
50
|
+
if let leeoError = e as? LeeoError {
|
|
51
|
+
switch leeoError {
|
|
52
|
+
case .sdkNotSetup: return ("SDK_NOT_SETUP", "Call setup first")
|
|
53
|
+
case .invalidOrNotFoundSdkKey: return ("INVALID_OR_NOT_FOUND_SDK_KEY", e.localizedDescription)
|
|
54
|
+
case .invalidDriverId: return ("INVALID_DRIVER_ID", e.localizedDescription)
|
|
55
|
+
case .networkNotAvailable: return ("NETWORK_NOT_AVAILABLE", e.localizedDescription)
|
|
56
|
+
case .leeoSDKError(let msg): return ("LEEO_SDK_ERROR", msg)
|
|
57
|
+
default: return ("LEEO_SDK_ERROR", e.localizedDescription)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return ("LEEO_SDK_ERROR", e.localizedDescription)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc func setup(
|
|
64
|
+
_ config: NSDictionary,
|
|
65
|
+
tripNotification: NSDictionary,
|
|
66
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
67
|
+
rejecter _: RCTPromiseRejectBlock
|
|
68
|
+
) {
|
|
69
|
+
guard let (configuration, notification) = Self.config(from: config, tripNotificationDict: tripNotification) else {
|
|
70
|
+
resolve(Self.error("INVALID_DRIVER_ID", "baseUrl, driverAttributes (firstName, lastName) required"))
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
Leeo.setup(configuration: configuration, tripNotification: notification) { success, error in
|
|
74
|
+
DispatchQueue.main.async {
|
|
75
|
+
if success { resolve(Self.success()) }
|
|
76
|
+
else {
|
|
77
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
78
|
+
resolve(Self.error(code, msg))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@objc func teardown(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
85
|
+
Leeo.teardown { DispatchQueue.main.async { resolve(Self.success()) } }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@objc func startTrip(_ trackingId: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
89
|
+
Leeo.startTrip(trackingId: trackingId ?? "") { success, error in
|
|
90
|
+
DispatchQueue.main.async {
|
|
91
|
+
if success { resolve(Self.success()) }
|
|
92
|
+
else {
|
|
93
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
94
|
+
resolve(Self.error(code, msg))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@objc func stopTrip(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
101
|
+
Leeo.stopTrip { success, error in
|
|
102
|
+
DispatchQueue.main.async {
|
|
103
|
+
if success { resolve(Self.success()) }
|
|
104
|
+
else {
|
|
105
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
106
|
+
resolve(Self.error(code, msg))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@objc func startShift(_ shiftId: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
113
|
+
Leeo.startShift(shiftId: shiftId) { success, error in
|
|
114
|
+
DispatchQueue.main.async {
|
|
115
|
+
if success { resolve(Self.success()) }
|
|
116
|
+
else {
|
|
117
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
118
|
+
resolve(Self.error(code, msg))
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@objc func stopShift(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
125
|
+
Leeo.stopShift { success, error in
|
|
126
|
+
DispatchQueue.main.async {
|
|
127
|
+
if success { resolve(Self.success()) }
|
|
128
|
+
else {
|
|
129
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
130
|
+
resolve(Self.error(code, msg))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@objc func wipeOut(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
137
|
+
Leeo.wipeOut { _ in DispatchQueue.main.async { resolve(Self.success()) } }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc func getBuildVersion(_ resolve: RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
141
|
+
resolve(Leeo.buildVersion)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@objc func openIncidentReportingWebPage(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
145
|
+
Leeo.openIncidentReportingWebPage { error in
|
|
146
|
+
DispatchQueue.main.async {
|
|
147
|
+
if error == nil { resolve(Self.success()) }
|
|
148
|
+
else {
|
|
149
|
+
let (code, msg) = Self.errorCode(from: error)
|
|
150
|
+
resolve(Self.error(code, msg))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@objc func getIncidentReportingURL(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
|
|
157
|
+
Leeo.getIncidentReportingURL { url, error in
|
|
158
|
+
DispatchQueue.main.async {
|
|
159
|
+
if let url = url { resolve(url) }
|
|
160
|
+
else if let error = error {
|
|
161
|
+
let (code, _) = Self.errorCode(from: error)
|
|
162
|
+
resolve(["success": false, "errorCode": code])
|
|
163
|
+
} else {
|
|
164
|
+
resolve(["success": false, "errorCode": "SDK_NOT_SETUP"])
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leeo React Native SDK
|
|
3
|
+
*
|
|
4
|
+
* Two flows: Manual (startTrip/stopTrip) and Shift (startShift/stopShift).
|
|
5
|
+
* Setup calls fleet-service: POST /fleet/v1/sdk/setup with sdk_key in body.
|
|
6
|
+
*/
|
|
7
|
+
/** Production API base URL. Omit or leave empty in config to use this. */
|
|
8
|
+
export declare const DEFAULT_BASE_URL = "https://api.leeoinsurance.com";
|
|
9
|
+
export interface LeeoConfiguration {
|
|
10
|
+
/** Fleet-service base URL. Defaults to [DEFAULT_BASE_URL]. No trailing slash. */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
sdkKey: string;
|
|
13
|
+
driverId: string;
|
|
14
|
+
driverAttributes: {
|
|
15
|
+
firstName: string;
|
|
16
|
+
lastName: string;
|
|
17
|
+
email: string;
|
|
18
|
+
phoneNumber: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface LeeoTripNotification {
|
|
22
|
+
title: string;
|
|
23
|
+
content: string;
|
|
24
|
+
iconId?: number;
|
|
25
|
+
}
|
|
26
|
+
export type LeeoOperationResult = {
|
|
27
|
+
success: true;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
errorCode: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const Leeo: {
|
|
34
|
+
setup(config: LeeoConfiguration, tripNotification: LeeoTripNotification): Promise<LeeoOperationResult>;
|
|
35
|
+
teardown(): Promise<LeeoOperationResult>;
|
|
36
|
+
startTrip(trackingId: string): Promise<LeeoOperationResult>;
|
|
37
|
+
stopTrip(): Promise<LeeoOperationResult>;
|
|
38
|
+
startShift(shiftId?: string): Promise<LeeoOperationResult>;
|
|
39
|
+
stopShift(): Promise<LeeoOperationResult>;
|
|
40
|
+
wipeOut(): Promise<LeeoOperationResult>;
|
|
41
|
+
getBuildVersion(): string;
|
|
42
|
+
openIncidentReportingWebPage(): Promise<LeeoOperationResult>;
|
|
43
|
+
getIncidentReportingURL(): Promise<string | {
|
|
44
|
+
success: false;
|
|
45
|
+
errorCode: string;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Leeo React Native SDK
|
|
4
|
+
*
|
|
5
|
+
* Two flows: Manual (startTrip/stopTrip) and Shift (startShift/stopShift).
|
|
6
|
+
* Setup calls fleet-service: POST /fleet/v1/sdk/setup with sdk_key in body.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Leeo = exports.DEFAULT_BASE_URL = void 0;
|
|
10
|
+
const react_native_1 = require("react-native");
|
|
11
|
+
const { LeeoSdk } = react_native_1.NativeModules;
|
|
12
|
+
/** Production API base URL. Omit or leave empty in config to use this. */
|
|
13
|
+
exports.DEFAULT_BASE_URL = 'https://api.leeoinsurance.com';
|
|
14
|
+
function normalizeConfig(config) {
|
|
15
|
+
const baseUrl = (config.baseUrl ?? '').trim();
|
|
16
|
+
return {
|
|
17
|
+
...config,
|
|
18
|
+
baseUrl: baseUrl || exports.DEFAULT_BASE_URL,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
exports.Leeo = {
|
|
22
|
+
setup(config, tripNotification) {
|
|
23
|
+
const normalized = normalizeConfig(config);
|
|
24
|
+
return LeeoSdk?.setup?.(normalized, tripNotification) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Native module not linked' });
|
|
25
|
+
},
|
|
26
|
+
teardown() {
|
|
27
|
+
return LeeoSdk?.teardown?.() ?? Promise.resolve({ success: true });
|
|
28
|
+
},
|
|
29
|
+
startTrip(trackingId) {
|
|
30
|
+
return LeeoSdk?.startTrip?.(trackingId) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
31
|
+
},
|
|
32
|
+
stopTrip() {
|
|
33
|
+
return LeeoSdk?.stopTrip?.() ?? Promise.resolve({ success: true });
|
|
34
|
+
},
|
|
35
|
+
startShift(shiftId) {
|
|
36
|
+
return LeeoSdk?.startShift?.(shiftId) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
37
|
+
},
|
|
38
|
+
stopShift() {
|
|
39
|
+
return LeeoSdk?.stopShift?.() ?? Promise.resolve({ success: true });
|
|
40
|
+
},
|
|
41
|
+
wipeOut() {
|
|
42
|
+
return LeeoSdk?.wipeOut?.() ?? Promise.resolve({ success: true });
|
|
43
|
+
},
|
|
44
|
+
getBuildVersion() {
|
|
45
|
+
return LeeoSdk?.getBuildVersion?.() ?? '0.1.0';
|
|
46
|
+
},
|
|
47
|
+
openIncidentReportingWebPage() {
|
|
48
|
+
return LeeoSdk?.openIncidentReportingWebPage?.() ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
49
|
+
},
|
|
50
|
+
getIncidentReportingURL() {
|
|
51
|
+
return LeeoSdk?.getIncidentReportingURL?.() ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED' });
|
|
52
|
+
},
|
|
53
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "leeo-react-native-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Leeo SDK for React Native – trip tracking and incident reporting",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index.ts",
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "npx tsc",
|
|
12
|
+
"prepare": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["react-native", "leeo", "trip", "drivequant"],
|
|
15
|
+
"repository": { "type": "git", "url": "" },
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "UNLICENSED",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"react-native": "*",
|
|
20
|
+
"typescript": "^5.0.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react-native": "*"
|
|
24
|
+
},
|
|
25
|
+
"optionalDependencies": {
|
|
26
|
+
"leeo-ios-sdk": "*"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"ios",
|
|
31
|
+
"android",
|
|
32
|
+
"LeeoSdk.podspec",
|
|
33
|
+
"lib"
|
|
34
|
+
]
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leeo React Native SDK
|
|
3
|
+
*
|
|
4
|
+
* Two flows: Manual (startTrip/stopTrip) and Shift (startShift/stopShift).
|
|
5
|
+
* Setup calls fleet-service: POST /fleet/v1/sdk/setup with sdk_key in body.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NativeModules } from 'react-native';
|
|
9
|
+
|
|
10
|
+
const { LeeoSdk } = NativeModules;
|
|
11
|
+
|
|
12
|
+
/** Production API base URL. Omit or leave empty in config to use this. */
|
|
13
|
+
export const DEFAULT_BASE_URL = 'https://api.leeoinsurance.com';
|
|
14
|
+
|
|
15
|
+
export interface LeeoConfiguration {
|
|
16
|
+
/** Fleet-service base URL. Defaults to [DEFAULT_BASE_URL]. No trailing slash. */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
sdkKey: string;
|
|
19
|
+
driverId: string;
|
|
20
|
+
driverAttributes: {
|
|
21
|
+
firstName: string;
|
|
22
|
+
lastName: string;
|
|
23
|
+
email: string;
|
|
24
|
+
phoneNumber: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LeeoTripNotification {
|
|
29
|
+
title: string;
|
|
30
|
+
content: string;
|
|
31
|
+
iconId?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type LeeoOperationResult = { success: true } | { success: false; errorCode: string; message: string };
|
|
35
|
+
|
|
36
|
+
function normalizeConfig(config: LeeoConfiguration): LeeoConfiguration & { baseUrl: string } {
|
|
37
|
+
const baseUrl = (config.baseUrl ?? '').trim();
|
|
38
|
+
return {
|
|
39
|
+
...config,
|
|
40
|
+
baseUrl: baseUrl || DEFAULT_BASE_URL,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Leeo = {
|
|
45
|
+
setup(
|
|
46
|
+
config: LeeoConfiguration,
|
|
47
|
+
tripNotification: LeeoTripNotification
|
|
48
|
+
): Promise<LeeoOperationResult> {
|
|
49
|
+
const normalized = normalizeConfig(config);
|
|
50
|
+
return LeeoSdk?.setup?.(normalized, tripNotification) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Native module not linked' });
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
teardown(): Promise<LeeoOperationResult> {
|
|
54
|
+
return LeeoSdk?.teardown?.() ?? Promise.resolve({ success: true });
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
startTrip(trackingId: string): Promise<LeeoOperationResult> {
|
|
58
|
+
return LeeoSdk?.startTrip?.(trackingId) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
stopTrip(): Promise<LeeoOperationResult> {
|
|
62
|
+
return LeeoSdk?.stopTrip?.() ?? Promise.resolve({ success: true });
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
startShift(shiftId?: string): Promise<LeeoOperationResult> {
|
|
66
|
+
return LeeoSdk?.startShift?.(shiftId) ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
stopShift(): Promise<LeeoOperationResult> {
|
|
70
|
+
return LeeoSdk?.stopShift?.() ?? Promise.resolve({ success: true });
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
wipeOut(): Promise<LeeoOperationResult> {
|
|
74
|
+
return LeeoSdk?.wipeOut?.() ?? Promise.resolve({ success: true });
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
getBuildVersion(): string {
|
|
78
|
+
return LeeoSdk?.getBuildVersion?.() ?? '0.1.0';
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
openIncidentReportingWebPage(): Promise<LeeoOperationResult> {
|
|
82
|
+
return LeeoSdk?.openIncidentReportingWebPage?.() ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED', message: 'Not implemented' });
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
getIncidentReportingURL(): Promise<string | { success: false; errorCode: string }> {
|
|
86
|
+
return LeeoSdk?.getIncidentReportingURL?.() ?? Promise.resolve({ success: false, errorCode: 'NOT_IMPLEMENTED' });
|
|
87
|
+
},
|
|
88
|
+
};
|