@bricks-soft/capacitor-location-tracking 0.0.0-stage → 8.0.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.
- package/LICENSE +21 -0
- package/README.md +2759 -2
- package/android/build.gradle +107 -0
- package/android/consumer-rules.pro +42 -0
- package/android/src/main/AndroidManifest.xml +52 -0
- package/android/src/main/java/com/brickssoft/locationtracking/LocationTrackingPlugin.kt +431 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingListener.kt +52 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingNative.kt +267 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/NativeListeners.kt +270 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/NativeThread.kt +57 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/BridgeServices.kt +64 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/CallResponder.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/OptionParsers.kt +281 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginHandlers.kt +541 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginPermissionHost.kt +76 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/WatchRegistry.kt +84 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/Config.kt +231 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigJson.kt +574 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigStore.kt +31 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigValidator.kt +170 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/RuntimeStateJson.kt +73 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/SharedPrefsConfigStore.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/State.kt +45 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/AppDispatchers.kt +21 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Clock.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Components.kt +217 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Constants.kt +78 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Errors.kt +27 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/EventBus.kt +39 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/ForceStopProbe.kt +63 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Iso8601.kt +63 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Logger.kt +68 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/RecordHooks.kt +47 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/TrackingEvent.kt +64 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqlSupport.kt +79 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteGeofenceStore.kt +144 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteLocationStore.kt +215 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/Stores.kt +45 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/TrackingDatabase.kt +111 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceInfoProvider.kt +104 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceMonitor.kt +335 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceMonitor.kt +36 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceStateMapping.kt +69 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/DefaultTrackingEngine.kt +1281 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/ElasticDistance.kt +68 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/LocationRequests.kt +74 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/MotionStateMachine.kt +291 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/StationaryRegion.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingEngine.kt +60 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingTimers.kt +90 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/DefaultGeofenceManager.kt +899 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/DwellTracker.kt +80 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/GeofenceManager.kt +33 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/PolygonMath.kt +194 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/DefaultHeartbeatScheduler.kt +713 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarmReceiver.kt +69 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarms.kt +100 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatScheduler.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatWindow.kt +179 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/AuthorizationManager.kt +213 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/BodyBuilder.kt +73 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSupport.kt +118 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSyncer.kt +18 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/OkHttpSyncer.kt +551 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/StrictJson.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/SyncPolicy.kt +109 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/TemplateRenderer.kt +143 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/FileLogger.kt +461 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFileProvider.kt +9 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFormatter.kt +148 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogStore.kt +30 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/DeviceModels.kt +176 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/GeofenceSpec.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/ModelJson.kt +550 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/Record.kt +106 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/TrackedLocation.kt +53 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/BackgroundRationaleDialog.kt +85 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/CurrentActivityTracker.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/DefaultPermissionManager.kt +333 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/PermissionManager.kt +54 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/DefaultPositionService.kt +277 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/PositionService.kt +39 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/PositionWatch.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultLocationProcessor.kt +93 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultOdometer.kt +99 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultRecordFactory.kt +166 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/Geo.kt +56 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/KalmanFilter.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/Processing.kt +48 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/Backends.kt +187 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/DefaultProviderFactory.kt +169 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/ProviderPackaging.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidActivityBackend.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceBackend.kt +181 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceReceiver.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidLocationBackend.kt +265 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidProviderBundle.kt +31 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/LocationProviders.kt +98 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityBackend.kt +80 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityReceiver.kt +62 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsErrors.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceBackend.kt +152 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceReceiver.kt +78 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsLocationBackend.kt +176 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsProviderBundle.kt +62 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsReceiverSupport.kt +88 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsTasks.kt +55 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityBackend.kt +71 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityReceiver.kt +57 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsErrors.kt +76 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceBackend.kt +116 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceReceiver.kt +67 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsIntents.kt +83 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsLocationBackend.kt +197 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsProviderBundle.kt +55 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsTasks.kt +44 -0
- package/android/src/main/java/com/brickssoft/locationtracking/record/RecordSink.kt +95 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/BootCountStore.kt +44 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/BootReceiver.kt +175 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/DefaultServiceController.kt +196 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/LocationTrackingService.kt +319 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationActionReceiver.kt +42 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationFactory.kt +338 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceCommands.kt +204 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceController.kt +22 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceDeps.kt +61 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/WhileInUseStartCheck.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/DefaultDeviceSettings.kt +79 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/DeviceSettings.kt +21 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/OemPowerManagers.kt +198 -0
- package/android/src/main/res/drawable/lt_ic_notification.xml +11 -0
- package/android/src/main/res/values/lt_strings.xml +11 -0
- package/android/src/main/res/xml/lt_file_paths.xml +5 -0
- package/dist/esm/definitions.d.ts +719 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/events.d.ts +50 -0
- package/dist/esm/events.js +52 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/plugin.d.ts +3 -0
- package/dist/esm/plugin.js +10 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/web/config.d.ts +19 -0
- package/dist/esm/web/config.js +243 -0
- package/dist/esm/web/config.js.map +1 -0
- package/dist/esm/web/device.d.ts +9 -0
- package/dist/esm/web/device.js +90 -0
- package/dist/esm/web/device.js.map +1 -0
- package/dist/esm/web/errors.d.ts +14 -0
- package/dist/esm/web/errors.js +19 -0
- package/dist/esm/web/errors.js.map +1 -0
- package/dist/esm/web/geolocation.d.ts +26 -0
- package/dist/esm/web/geolocation.js +117 -0
- package/dist/esm/web/geolocation.js.map +1 -0
- package/dist/esm/web/location.d.ts +7 -0
- package/dist/esm/web/location.js +70 -0
- package/dist/esm/web/location.js.map +1 -0
- package/dist/esm/web.d.ts +164 -0
- package/dist/esm/web.js +328 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +945 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +948 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +60 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.bridge
|
|
2
|
+
|
|
3
|
+
import com.brickssoft.locationtracking.core.ErrorCode
|
|
4
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
5
|
+
import com.brickssoft.locationtracking.core.TrackingException
|
|
6
|
+
import com.getcapacitor.JSObject
|
|
7
|
+
import com.getcapacitor.PluginCall
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
|
|
10
|
+
/** A promise rejection: `call.reject(message, code.name)`. */
|
|
11
|
+
internal data class Rejection(val message: String, val code: ErrorCode) {
|
|
12
|
+
companion object {
|
|
13
|
+
/** [TrackingException] keeps its code; anything else is [ErrorCode.INTERNAL]. */
|
|
14
|
+
fun of(error: Throwable): Rejection {
|
|
15
|
+
val message = error.message?.takeIf { it.isNotBlank() }
|
|
16
|
+
return when (error) {
|
|
17
|
+
is TrackingException -> Rejection(message ?: error.code.name, error.code)
|
|
18
|
+
else -> Rejection(message ?: error.javaClass.simpleName, ErrorCode.INTERNAL)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Settles a [PluginCall] with a handler result (null = `void`) or error. */
|
|
25
|
+
internal object CallResponder {
|
|
26
|
+
private const val TAG = "LT.Bridge"
|
|
27
|
+
|
|
28
|
+
fun resolve(call: PluginCall, result: JSONObject?) {
|
|
29
|
+
if (result == null) call.resolve() else call.resolve(JSObject.fromJSONObject(result))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun reject(call: PluginCall, error: Throwable) {
|
|
33
|
+
val rejection = Rejection.of(error)
|
|
34
|
+
if (rejection.code == ErrorCode.INTERNAL) {
|
|
35
|
+
Logger.e(TAG, "${call.methodName} failed", error)
|
|
36
|
+
} else {
|
|
37
|
+
Logger.d(TAG, "${call.methodName} rejected: ${rejection.code} ${rejection.message}")
|
|
38
|
+
}
|
|
39
|
+
call.reject(rejection.message, rejection.code.name)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.bridge
|
|
2
|
+
|
|
3
|
+
import com.brickssoft.locationtracking.core.ErrorCode
|
|
4
|
+
import com.brickssoft.locationtracking.core.Iso8601
|
|
5
|
+
import com.brickssoft.locationtracking.core.LogLevel
|
|
6
|
+
import com.brickssoft.locationtracking.core.TrackingException
|
|
7
|
+
import com.brickssoft.locationtracking.logging.LogQuery
|
|
8
|
+
import com.brickssoft.locationtracking.model.DesiredAccuracy
|
|
9
|
+
import com.brickssoft.locationtracking.model.GeofenceJson
|
|
10
|
+
import com.brickssoft.locationtracking.model.GeofenceSpec
|
|
11
|
+
import com.brickssoft.locationtracking.model.RecordEvent
|
|
12
|
+
import com.brickssoft.locationtracking.permission.PermissionType
|
|
13
|
+
import com.brickssoft.locationtracking.position.CurrentPositionOptions
|
|
14
|
+
import com.brickssoft.locationtracking.position.WatchPositionOptions
|
|
15
|
+
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
16
|
+
import org.json.JSONArray
|
|
17
|
+
import org.json.JSONObject
|
|
18
|
+
import kotlin.math.floor
|
|
19
|
+
|
|
20
|
+
/** `uploadLog` options; [paramsJson] is JSON object text. */
|
|
21
|
+
internal data class UploadLogOptions(val url: String, val headers: Map<String, String>, val paramsJson: String?)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses and validates JS call options into Kotlin option types.
|
|
25
|
+
*
|
|
26
|
+
* A missing key or JSON null means "use the default". A present value of the wrong type or out of range throws
|
|
27
|
+
* [TrackingException] with [ErrorCode.INVALID_ARGUMENT]. Numbers must be JSON numbers (no numeric strings) and
|
|
28
|
+
* booleans must be JSON booleans.
|
|
29
|
+
*/
|
|
30
|
+
internal object OptionParsers {
|
|
31
|
+
/** `requestPermissions` default: every type, in the order of the JS contract (background last). */
|
|
32
|
+
val DEFAULT_PERMISSION_ORDER: List<PermissionType> = listOf(
|
|
33
|
+
PermissionType.LOCATION,
|
|
34
|
+
PermissionType.NOTIFICATIONS,
|
|
35
|
+
PermissionType.ACTIVITY_RECOGNITION,
|
|
36
|
+
PermissionType.BACKGROUND_LOCATION,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
private val LATITUDE = -90.0..90.0
|
|
40
|
+
private val LONGITUDE = -180.0..180.0
|
|
41
|
+
private val HEADING = 0.0..360.0
|
|
42
|
+
|
|
43
|
+
// ---- typed options
|
|
44
|
+
|
|
45
|
+
/** JS `CurrentPositionOptions`. */
|
|
46
|
+
fun currentPositionOptions(json: JSONObject): CurrentPositionOptions {
|
|
47
|
+
val defaults = CurrentPositionOptions()
|
|
48
|
+
return CurrentPositionOptions(
|
|
49
|
+
samples = optInt(json, "samples", min = 1) ?: defaults.samples,
|
|
50
|
+
timeoutMs = optLong(json, "timeout", min = 1),
|
|
51
|
+
maximumAgeMs = optLong(json, "maximumAge", min = 0) ?: defaults.maximumAgeMs,
|
|
52
|
+
desiredAccuracy = optAccuracy(json) ?: defaults.desiredAccuracy,
|
|
53
|
+
persist = optBoolean(json, "persist") ?: defaults.persist,
|
|
54
|
+
extras = optObject(json, "extras")?.toString(),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** JS `WatchPositionOptions`. */
|
|
59
|
+
fun watchPositionOptions(json: JSONObject): WatchPositionOptions {
|
|
60
|
+
val defaults = WatchPositionOptions()
|
|
61
|
+
return WatchPositionOptions(
|
|
62
|
+
intervalMs = optLong(json, "interval", min = 0) ?: defaults.intervalMs,
|
|
63
|
+
desiredAccuracy = optAccuracy(json) ?: defaults.desiredAccuracy,
|
|
64
|
+
persist = optBoolean(json, "persist") ?: defaults.persist,
|
|
65
|
+
extras = optObject(json, "extras")?.toString(),
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* JS `Geofence` -> [GeofenceSpec] via [GeofenceJson], plus value checks: coordinates in range, a circle's
|
|
71
|
+
* radius > 0, a polygon's >= 3 vertices, loiteringDelay >= 0, and typed notify flags and extras.
|
|
72
|
+
*/
|
|
73
|
+
fun geofence(json: JSONObject): GeofenceSpec {
|
|
74
|
+
// GeofenceJson is lenient (numeric strings, any identifier coerced to text); enforce JSON types first.
|
|
75
|
+
val id = requireString(json, "identifier", "geofence.identifier")
|
|
76
|
+
for (key in listOf("latitude", "longitude", "radius")) optNumber(json, key, "geofence '$id' $key")
|
|
77
|
+
val spec = GeofenceJson.fromJson(json)
|
|
78
|
+
for (key in listOf("notifyOnEntry", "notifyOnExit", "notifyOnDwell")) {
|
|
79
|
+
optBoolean(json, key, "geofence '$id' $key")
|
|
80
|
+
}
|
|
81
|
+
optObject(json, "extras", "geofence '$id' extras")
|
|
82
|
+
optLong(json, "loiteringDelay", min = 0, path = "geofence '$id' loiteringDelay")
|
|
83
|
+
val vertices = spec.vertices
|
|
84
|
+
if (vertices == null) {
|
|
85
|
+
checkRange(spec.latitude, LATITUDE, "geofence '$id' latitude")
|
|
86
|
+
checkRange(spec.longitude, LONGITUDE, "geofence '$id' longitude")
|
|
87
|
+
if (!(spec.radius > 0f) || spec.radius.isInfinite()) invalid("geofence '$id' radius must be > 0")
|
|
88
|
+
} else {
|
|
89
|
+
if (vertices.size < 3) invalid("geofence '$id' needs at least 3 vertices")
|
|
90
|
+
vertices.forEachIndexed { i, v ->
|
|
91
|
+
checkRange(v.latitude, LATITUDE, "geofence '$id' vertices[$i] latitude")
|
|
92
|
+
checkRange(v.longitude, LONGITUDE, "geofence '$id' vertices[$i] longitude")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return spec
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The required `geofences` array of `addGeofences`. */
|
|
99
|
+
fun geofences(json: JSONObject): List<GeofenceSpec> {
|
|
100
|
+
val array = requireArray(json, "geofences")
|
|
101
|
+
return (0 until array.length()).map { i ->
|
|
102
|
+
val item = array.opt(i) as? JSONObject ?: invalid("geofences[$i] must be an object")
|
|
103
|
+
geofence(item)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The required `location` (JS `InsertLocationInput`) of `insertLocation`, validated and returned as is for
|
|
109
|
+
* `RecordFactory.fromExternal`.
|
|
110
|
+
*/
|
|
111
|
+
fun insertLocationInput(json: JSONObject): JSONObject {
|
|
112
|
+
val location = requireObject(json, "location")
|
|
113
|
+
val coords = requireObject(location, "coords", "location.coords")
|
|
114
|
+
requireNumber(coords, "latitude", "location.coords.latitude").also {
|
|
115
|
+
checkRange(it, LATITUDE, "location.coords.latitude")
|
|
116
|
+
}
|
|
117
|
+
requireNumber(coords, "longitude", "location.coords.longitude").also {
|
|
118
|
+
checkRange(it, LONGITUDE, "location.coords.longitude")
|
|
119
|
+
}
|
|
120
|
+
optNumber(coords, "altitude", "location.coords.altitude")
|
|
121
|
+
for (key in listOf("accuracy", "altitude_accuracy", "speed", "speed_accuracy", "heading_accuracy")) {
|
|
122
|
+
val value = optNumber(coords, key, "location.coords.$key")
|
|
123
|
+
if (value != null && value < 0) invalid("location.coords.$key must be >= 0")
|
|
124
|
+
}
|
|
125
|
+
optNumber(coords, "heading", "location.coords.heading")?.let {
|
|
126
|
+
checkRange(it, HEADING, "location.coords.heading")
|
|
127
|
+
}
|
|
128
|
+
optString(location, "timestamp", "location.timestamp")?.let {
|
|
129
|
+
if (Iso8601.parse(it) == null) invalid("location.timestamp must be an ISO-8601 date, got '$it'")
|
|
130
|
+
}
|
|
131
|
+
optString(location, "event", "location.event")?.let {
|
|
132
|
+
if (RecordEvent.fromWire(it) == null) invalid("location.event '$it' is not a record event")
|
|
133
|
+
}
|
|
134
|
+
optBoolean(location, "is_moving", "location.is_moving")
|
|
135
|
+
optObject(location, "extras", "location.extras")
|
|
136
|
+
return location
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** JS `LogQuery`; `order` defaults to ascending. */
|
|
140
|
+
fun logQuery(json: JSONObject): LogQuery {
|
|
141
|
+
val start = optLong(json, "start", min = 0)
|
|
142
|
+
val end = optLong(json, "end", min = 0)
|
|
143
|
+
if (start != null && end != null && start > end) invalid("'start' must not be after 'end'")
|
|
144
|
+
val level = optString(json, "level")?.let { LogLevel.fromWire(it) ?: invalid("unknown log level '$it'") }
|
|
145
|
+
val ascending = when (val order = optString(json, "order")?.lowercase()) {
|
|
146
|
+
null, "asc" -> true
|
|
147
|
+
"desc" -> false
|
|
148
|
+
else -> invalid("'order' must be 'asc' or 'desc', got '$order'")
|
|
149
|
+
}
|
|
150
|
+
val limit = optInt(json, "limit", min = 0)
|
|
151
|
+
return LogQuery(start = start, end = end, level = level, limit = limit, ascending = ascending)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The required `level` of `log()`: any [LogLevel] except OFF. */
|
|
155
|
+
fun logLevel(json: JSONObject): LogLevel {
|
|
156
|
+
val wire = requireString(json, "level")
|
|
157
|
+
val level = LogLevel.fromWire(wire)
|
|
158
|
+
if (level == null || level == LogLevel.OFF) invalid("'level' must be error, warn, info, debug or verbose")
|
|
159
|
+
return level
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* `requestPermissions({permissions?})`: missing -> [DEFAULT_PERMISSION_ORDER]; otherwise the given types,
|
|
164
|
+
* de-duplicated, in that canonical order.
|
|
165
|
+
*/
|
|
166
|
+
fun permissionTypes(json: JSONObject): List<PermissionType> {
|
|
167
|
+
val array = optArray(json, "permissions") ?: return DEFAULT_PERMISSION_ORDER
|
|
168
|
+
val requested = (0 until array.length()).map { i ->
|
|
169
|
+
val alias = array.opt(i)
|
|
170
|
+
PermissionType.fromAlias(alias as? String) ?: invalid("unknown permission type '$alias'")
|
|
171
|
+
}.toSet()
|
|
172
|
+
return DEFAULT_PERMISSION_ORDER.filter { it in requested }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** `uploadLog({url, headers?, params?})`: url must be http(s). */
|
|
176
|
+
fun uploadLogOptions(json: JSONObject): UploadLogOptions {
|
|
177
|
+
val url = requireString(json, "url")
|
|
178
|
+
if (url.toHttpUrlOrNull() == null) invalid("'url' must be an http(s) URL, got '$url'")
|
|
179
|
+
return UploadLogOptions(url, stringMap(json, "headers"), optObject(json, "params")?.toString())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* An optional object of header-like values; strings, numbers and booleans are converted to text,
|
|
184
|
+
* null values are dropped.
|
|
185
|
+
*/
|
|
186
|
+
fun stringMap(json: JSONObject, key: String): Map<String, String> {
|
|
187
|
+
val obj = optObject(json, key) ?: return emptyMap()
|
|
188
|
+
val out = LinkedHashMap<String, String>()
|
|
189
|
+
val keys = obj.keys()
|
|
190
|
+
while (keys.hasNext()) {
|
|
191
|
+
val name = keys.next()
|
|
192
|
+
if (obj.isNull(name)) continue
|
|
193
|
+
when (val value = obj.opt(name)) {
|
|
194
|
+
is String, is Number, is Boolean -> out[name] = value.toString()
|
|
195
|
+
else -> invalid("'$key.$name' must be a string")
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---- primitives (path = the name used in error messages)
|
|
202
|
+
|
|
203
|
+
fun optObject(json: JSONObject, key: String, path: String = key): JSONObject? = when (val v = value(json, key)) {
|
|
204
|
+
null -> null
|
|
205
|
+
is JSONObject -> v
|
|
206
|
+
else -> invalid("'$path' must be an object")
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun requireObject(json: JSONObject, key: String, path: String = key): JSONObject =
|
|
210
|
+
optObject(json, key, path) ?: invalid("'$path' is required")
|
|
211
|
+
|
|
212
|
+
fun optArray(json: JSONObject, key: String, path: String = key): JSONArray? = when (val v = value(json, key)) {
|
|
213
|
+
null -> null
|
|
214
|
+
is JSONArray -> v
|
|
215
|
+
else -> invalid("'$path' must be an array")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fun requireArray(json: JSONObject, key: String, path: String = key): JSONArray =
|
|
219
|
+
optArray(json, key, path) ?: invalid("'$path' is required")
|
|
220
|
+
|
|
221
|
+
fun optString(json: JSONObject, key: String, path: String = key): String? = when (val v = value(json, key)) {
|
|
222
|
+
null -> null
|
|
223
|
+
is String -> v
|
|
224
|
+
else -> invalid("'$path' must be a string")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** A required, non-blank string. */
|
|
228
|
+
fun requireString(json: JSONObject, key: String, path: String = key): String {
|
|
229
|
+
val v = optString(json, key, path) ?: invalid("'$path' is required")
|
|
230
|
+
if (v.isBlank()) invalid("'$path' must not be empty")
|
|
231
|
+
return v
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fun optBoolean(json: JSONObject, key: String, path: String = key): Boolean? = when (val v = value(json, key)) {
|
|
235
|
+
null -> null
|
|
236
|
+
is Boolean -> v
|
|
237
|
+
else -> invalid("'$path' must be a boolean")
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fun requireBoolean(json: JSONObject, key: String, path: String = key): Boolean =
|
|
241
|
+
optBoolean(json, key, path) ?: invalid("'$path' is required")
|
|
242
|
+
|
|
243
|
+
/** A finite number. */
|
|
244
|
+
fun optNumber(json: JSONObject, key: String, path: String = key): Double? = when (val v = value(json, key)) {
|
|
245
|
+
null -> null
|
|
246
|
+
is Number -> v.toDouble().takeIf { !it.isNaN() && !it.isInfinite() }
|
|
247
|
+
?: invalid("'$path' must be a finite number")
|
|
248
|
+
else -> invalid("'$path' must be a number")
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fun requireNumber(json: JSONObject, key: String, path: String = key): Double =
|
|
252
|
+
optNumber(json, key, path) ?: invalid("'$path' is required")
|
|
253
|
+
|
|
254
|
+
/** A number >= [min], truncated to whole milliseconds (or any other whole unit). */
|
|
255
|
+
fun optLong(json: JSONObject, key: String, min: Long, path: String = key): Long? {
|
|
256
|
+
val v = optNumber(json, key, path) ?: return null
|
|
257
|
+
if (v < min) invalid("'$path' must be >= $min")
|
|
258
|
+
if (v >= Long.MAX_VALUE.toDouble()) invalid("'$path' is too large")
|
|
259
|
+
return v.toLong()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** A whole number in [min, [Int.MAX_VALUE]]. */
|
|
263
|
+
fun optInt(json: JSONObject, key: String, min: Int, path: String = key): Int? {
|
|
264
|
+
val v = optNumber(json, key, path) ?: return null
|
|
265
|
+
if (v != floor(v)) invalid("'$path' must be a whole number")
|
|
266
|
+
if (v < min || v > Int.MAX_VALUE) invalid("'$path' must be between $min and ${Int.MAX_VALUE}")
|
|
267
|
+
return v.toInt()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private fun optAccuracy(json: JSONObject): DesiredAccuracy? = optString(json, "desiredAccuracy")?.let {
|
|
271
|
+
DesiredAccuracy.fromWire(it) ?: invalid("'desiredAccuracy' must be high, balanced, low or passive, got '$it'")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private fun checkRange(value: Double, range: ClosedFloatingPointRange<Double>, path: String) {
|
|
275
|
+
if (value !in range) invalid("'$path' must be within ${range.start}..${range.endInclusive}, got $value")
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private fun value(json: JSONObject, key: String): Any? = if (json.isNull(key)) null else json.opt(key)
|
|
279
|
+
|
|
280
|
+
fun invalid(message: String): Nothing = throw TrackingException(ErrorCode.INVALID_ARGUMENT, message)
|
|
281
|
+
}
|