From 244f70b56762fb11f046fa2d667cf5d185626f1a Mon Sep 17 00:00:00 2001 From: wengki81 Date: Tue, 6 Jan 2026 11:08:16 +0800 Subject: [PATCH] 20260106-01 --- .../plugin/geolocation/DumonGeolocation.kt | 206 ++++++++++++------ .../geolocation/gps/GpsStatusManager.kt | 204 +++++++++-------- .../geolocation/imu/ImuSensorManager.kt | 14 +- .../dumon/plugin/geolocation/utils/BgPrefs.kt | 5 - .../geolocation/utils/PermissionUtils.kt | 28 ++- .../wifi/WifiPositioningManager.kt | 18 +- dist/esm/definitions.js.map | 2 +- dist/esm/index.d.ts | 6 +- dist/esm/index.js | 17 +- dist/esm/index.js.map | 2 +- dist/plugin.cjs.js | 105 --------- dist/plugin.cjs.js.map | 1 - dist/plugin.js | 108 --------- dist/plugin.js.map | 1 - src/index.ts | 20 +- 15 files changed, 326 insertions(+), 411 deletions(-) delete mode 100644 dist/plugin.cjs.js delete mode 100644 dist/plugin.cjs.js.map delete mode 100644 dist/plugin.js delete mode 100644 dist/plugin.js.map diff --git a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt index 65fafa4..f11223c 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt @@ -40,13 +40,29 @@ import com.dumon.plugin.geolocation.utils.AuthStore @CapacitorPlugin( name = "DumonGeolocation", permissions = [ - Permission(strings = [ - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_WIFI_STATE, - Manifest.permission.CHANGE_WIFI_STATE, - Manifest.permission.NEARBY_WIFI_DEVICES - ]) + Permission( + alias = "location", + strings = [ + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ] + ), + Permission( + alias = "wifi", + strings = [ + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.NEARBY_WIFI_DEVICES + ] + ), + Permission( + alias = "backgroundLocation", + strings = [Manifest.permission.ACCESS_BACKGROUND_LOCATION] + ), + Permission( + alias = "notifications", + strings = [Manifest.permission.POST_NOTIFICATIONS] + ) ] ) class DumonGeolocation : Plugin() { @@ -76,6 +92,7 @@ class DumonGeolocation : Plugin() { private var prevTimestamp: Long = 0L // private val significantChangeThreshold = 0.00007 // ~7 meters private var significantChangeThreshold = 7.0 // ~7 meters + private var gpsMinDistanceMeters = 7.0 private var speedChangeThreshold = 0.5f // m/s private var directionChangeThreshold = 0.17f // ~10 deg @@ -97,6 +114,10 @@ class DumonGeolocation : Plugin() { private var emitGnssStatus = false private var suppressMockedUpdates = false private var keepScreenOn = false + private var lastSingleFixRequestTs: Long = 0L + private val minSingleFixIntervalMs: Long = 1000L + private var pendingPermissionAlias: String? = null + private var pendingBackgroundPermissionAlias: String? = null private var currentTrackingMode = GpsTrackingMode.NORMAL @@ -183,15 +204,19 @@ class DumonGeolocation : Plugin() { @PluginMethod fun startPositioning(call: PluginCall) { - if (!PermissionUtils.hasLocationAndWifiPermissions(context)) { - call.reject("Required permissions not granted") + if (!PermissionUtils.hasLocationPermissions(context)) { + call.reject("Location permission not granted") return } gpsManager?.start() imuManager?.start() - wifiManager?.setEnableRtt(enableWifiRtt) - wifiManager?.startPeriodicScan(wifiScanIntervalMs) + if (PermissionUtils.hasWifiScanPermissions(context)) { + wifiManager?.setEnableRtt(enableWifiRtt) + wifiManager?.startPeriodicScan(wifiScanIntervalMs) + } else { + LogUtils.w("DUMON_GEOLOCATION", "Wi-Fi scan permissions not granted; skipping Wi-Fi scans") + } applyKeepScreenOn(keepScreenOn) call.resolve() } @@ -212,6 +237,13 @@ class DumonGeolocation : Plugin() { val hasFine = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED val hasCoarse = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED + val now = System.currentTimeMillis() + if (now - lastSingleFixRequestTs < minSingleFixIntervalMs) { + call.resolve(buildPositionData()) + return + } + lastSingleFixRequestTs = now + val manager = gpsManager if (manager == null || (!hasFine && !hasCoarse)) { // No manager or no permissions — fallback immediately @@ -245,36 +277,10 @@ class DumonGeolocation : Plugin() { @PluginMethod fun checkAndRequestPermissions(call: PluginCall) { -// requestAllPermissions(call, "checkAndRequestPermissions") - val isLocationGranted = - ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED - val isWifiGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) == PackageManager.PERMISSION_GRANTED - } else { - true - } - - if (!isLocationGranted || !isWifiGranted) { - requestAllPermissions(call, "onPermissionResult") + if (!requestNextForegroundPermission(call)) { return } - - val locationStatus = PermissionUtils.getPermissionStatus( - ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) - ) - val wifiStatus = PermissionUtils.getPermissionStatus( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) - ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) - else - PackageManager.PERMISSION_GRANTED - ) - - val result = JSObject().apply { - put("location", locationStatus) - put("wifi", wifiStatus) - } - - call.resolve(result) + call.resolve(buildForegroundPermissionResult()) } @PluginMethod @@ -343,11 +349,13 @@ class DumonGeolocation : Plugin() { val mode = call.getString("mode") ?: "normal" if (mode == "driving") { gpsManager?.startContinuousMode() + gpsManager?.setMinDistanceMeters(0.0) currentTrackingMode = GpsTrackingMode.DRIVING startDrivingEmitLoop() LogUtils.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)") } else { gpsManager?.startPollingMode() + gpsManager?.setMinDistanceMeters(gpsMinDistanceMeters) currentTrackingMode = GpsTrackingMode.NORMAL stopDrivingEmitLoop() LogUtils.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)") @@ -357,27 +365,21 @@ class DumonGeolocation : Plugin() { @PermissionCallback private fun onPermissionResult(call: PluginCall) { - val locationStatus = PermissionUtils.getPermissionStatus( - ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) - ) - val wifiStatus = PermissionUtils.getPermissionStatus( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) - ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) - else - PackageManager.PERMISSION_GRANTED - ) - - val result = JSObject().apply { - put("location", locationStatus) - put("wifi", wifiStatus) + if (!requestNextForegroundPermission(call)) { + return } - - call.resolve(result) + call.resolve(buildForegroundPermissionResult()) } @PluginMethod fun setOptions(call: PluginCall) { - call.getDouble("distanceThresholdMeters")?.let { significantChangeThreshold = it } + call.getDouble("distanceThresholdMeters")?.let { + significantChangeThreshold = it + gpsMinDistanceMeters = it + if (currentTrackingMode == GpsTrackingMode.NORMAL) { + gpsManager?.setMinDistanceMeters(it) + } + } call.getDouble("speedChangeThreshold")?.let { speedChangeThreshold = it.toFloat() } call.getDouble("directionChangeThreshold")?.let { directionChangeThreshold = it.toFloat() } call.getInt("emitDebounceMs")?.let { @@ -480,19 +482,15 @@ class DumonGeolocation : Plugin() { @PluginMethod fun startBackgroundTracking(call: PluginCall) { + if (!ensureBackgroundPermissions(call)) { + return + } val title = call.getString("title") ?: "Location tracking active" val text = call.getString("text") ?: "Updating location in background" val channelId = call.getString("channelId") ?: "DUMON_GEO_BG" val channelName = call.getString("channelName") ?: "Dumon Geolocation" val postUrl = call.getString("postUrl") - val fine = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED - val coarse = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED - if (!fine && !coarse) { - call.reject("Location permission not granted") - return - } - val intent = Intent(context, BackgroundLocationService::class.java).apply { putExtra(BackgroundLocationService.EXTRA_CHANNEL_ID, channelId) putExtra(BackgroundLocationService.EXTRA_CHANNEL_NAME, channelName) @@ -800,4 +798,88 @@ class DumonGeolocation : Plugin() { applyKeepScreenOn(false) super.handleOnDestroy() } + + private fun buildForegroundPermissionResult(): JSObject { + val locationStatus = if (PermissionUtils.hasLocationPermissions(context)) "granted" else "denied" + val wifiStatus = if (PermissionUtils.hasWifiScanPermissions(context)) "granted" else "denied" + return JSObject().apply { + put("location", locationStatus) + put("wifi", wifiStatus) + } + } + + private fun getMissingForegroundPermissionAlias(): String? { + return when { + !PermissionUtils.hasLocationPermissions(context) -> "location" + !PermissionUtils.hasWifiScanPermissions(context) -> "wifi" + else -> null + } + } + + private fun requestNextForegroundPermission(call: PluginCall): Boolean { + val missing = getMissingForegroundPermissionAlias() + if (missing == null) { + pendingPermissionAlias = null + return true + } + + if (pendingPermissionAlias == missing) { + pendingPermissionAlias = null + call.resolve(buildForegroundPermissionResult()) + return false + } + + pendingPermissionAlias = missing + call.save() + requestPermissionForAlias(missing, call, "onPermissionResult") + return false + } + + private fun getMissingBackgroundPermissionAlias(): String? { + if (!PermissionUtils.hasLocationPermissions(context)) { + return "location" + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val bgGranted = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED + if (!bgGranted) return "backgroundLocation" + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val notifGranted = ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + if (!notifGranted) return "notifications" + } + return null + } + + private fun ensureBackgroundPermissions(call: PluginCall): Boolean { + val missing = getMissingBackgroundPermissionAlias() + if (missing == null) { + pendingBackgroundPermissionAlias = null + return true + } + + if (pendingBackgroundPermissionAlias == missing) { + pendingBackgroundPermissionAlias = null + val msg = when (missing) { + "location" -> "Location permission not granted" + "backgroundLocation" -> "Background location permission not granted" + "notifications" -> "Notification permission not granted" + else -> "Required permission not granted" + } + call.reject(msg) + return false + } + + pendingBackgroundPermissionAlias = missing + call.save() + requestPermissionForAlias(missing, call, "onBackgroundPermissionResult") + return false + } + + @PermissionCallback + private fun onBackgroundPermissionResult(call: PluginCall) { + if (!ensureBackgroundPermissions(call)) { + return + } + startBackgroundTracking(call) + } } diff --git a/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt b/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt index 199f09d..d68c440 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt @@ -32,7 +32,7 @@ class GpsStatusManager( private val handler = Handler(Looper.getMainLooper()) private var pollingIntervalMs: Long = 1000L // Default NORMAL mode - private var isPolling = false + private var minDistanceMeters: Float = 0f private var currentMode = GpsTrackingMode.NORMAL private var continuousListener: LocationListener? = null @@ -153,83 +153,17 @@ class GpsStatusManager( mainHandler.postDelayed(timeoutRunnable, timeoutMs) } - private fun pollOnceAndEmit() { - val oneShotListener = object : LocationListener { - override fun onLocationChanged(location: Location) { - val providerTag = when (location.provider) { - LocationManager.GPS_PROVIDER -> "[GPS]" - LocationManager.NETWORK_PROVIDER -> "[NET]" - else -> "[OTHER]" - } - - val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis()) - val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - location.isMock - } else { - location.isFromMockProvider - } - - val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked" - LogUtils.d("GPS_LOCATION", info) - - onLocationUpdate(location, isMocked) - locationManager.removeUpdates(this) - } - - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} - override fun onProviderEnabled(provider: String) {} - override fun onProviderDisabled(provider: String) {} - } - val hasFine = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED - val hasCoarse = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED - - var requested = false - if (hasFine) { - try { - locationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - 0L, - 0f, - oneShotListener - ) - requested = true - } catch (e: Exception) { - LogUtils.e("GPS_STATUS", "Failed GPS one-shot request: ${e.message}") - } - } - - if (hasCoarse) { - try { - locationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, - 0L, - 0f, - oneShotListener - ) - requested = true - } catch (e: Exception) { - LogUtils.e("GPS_STATUS", "Failed NETWORK one-shot request: ${e.message}") - } - } - - if (!requested) { - LogUtils.e("GPS_STATUS", "pollOnceAndEmit: no provider requested (missing permissions)") - } - } - - private val pollingRunnable = object : Runnable { - override fun run() { - if (!isPolling) return - pollOnceAndEmit() - handler.postDelayed(this, pollingIntervalMs) - } - } - fun setPollingInterval(intervalMs: Long) { this.pollingIntervalMs = intervalMs - if (isPolling) { - handler.removeCallbacks(pollingRunnable) - handler.postDelayed(pollingRunnable, pollingIntervalMs) + if (currentMode == GpsTrackingMode.NORMAL) { + startContinuousUpdates(pollingIntervalMs, minDistanceMeters) + } + } + + fun setMinDistanceMeters(distanceMeters: Double) { + minDistanceMeters = distanceMeters.toFloat().coerceAtLeast(0f) + if (currentMode == GpsTrackingMode.NORMAL) { + startContinuousUpdates(pollingIntervalMs, minDistanceMeters) } } @@ -238,25 +172,30 @@ class GpsStatusManager( stop() // Reset dulu currentMode = mode - if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + val hasFine = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + val hasCoarse = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED + if (!hasFine && !hasCoarse) { LogUtils.e("GPS_STATUS", "Missing location permissions") return } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - locationManager.registerGnssStatusCallback( - ContextCompat.getMainExecutor(context), - gnssStatusCallback - ) - } else { - locationManager.registerGnssStatusCallback(gnssStatusCallback, handler) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + locationManager.registerGnssStatusCallback( + ContextCompat.getMainExecutor(context), + gnssStatusCallback + ) + } else { + locationManager.registerGnssStatusCallback(gnssStatusCallback, handler) + } + } catch (e: Exception) { + LogUtils.e("GPS_STATUS", "Failed to register GNSS status callback: ${e.message}") } if (mode == GpsTrackingMode.DRIVING) { - startContinuousUpdates() + startContinuousUpdates(0L, 0f) } else { - isPolling = true - handler.post(pollingRunnable) + startContinuousUpdates(pollingIntervalMs, minDistanceMeters) } // Fallback lokasi terakhir @@ -274,12 +213,25 @@ class GpsStatusManager( LogUtils.d("GPS_STATUS", "GPS started with mode: $mode") } - private fun startContinuousUpdates() { - if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - LogUtils.e("GPS_STATUS", "Missing ACCESS_FINE_LOCATION permission") + private fun startContinuousUpdates(minTimeMs: Long, minDistanceMeters: Float) { + val hasFine = ActivityCompat.checkSelfPermission( + context, + android.Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + val hasCoarse = ActivityCompat.checkSelfPermission( + context, + android.Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + + if (!hasFine && !hasCoarse) { + LogUtils.e("GPS_STATUS", "Missing location permissions") return } + continuousListener?.let { + try { locationManager.removeUpdates(it) } catch (_: Exception) {} + } + continuousListener = object : LocationListener { override fun onLocationChanged(location: Location) { val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider @@ -291,31 +243,77 @@ class GpsStatusManager( override fun onProviderDisabled(provider: String) {} } - locationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - 0L, - 0f, - continuousListener!! - ) + val gpsEnabled = try { + locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + } catch (_: Exception) { + false + } + val netEnabled = try { + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } catch (_: Exception) { + false + } - LogUtils.d("GPS_STATUS", "Started continuous location updates") + if (!gpsEnabled && !netEnabled) { + LogUtils.w("GPS_STATUS", "No provider enabled; skip location requests") + } + + var requested = false + if (hasFine && gpsEnabled) { + try { + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + minTimeMs, + minDistanceMeters, + continuousListener!! + ) + requested = true + } catch (e: Exception) { + LogUtils.e("GPS_STATUS", "Failed GPS request: ${e.message}") + } + } else if (hasFine) { + LogUtils.w("GPS_STATUS", "GPS provider disabled; skip GPS request") + } + + if (hasCoarse && netEnabled) { + try { + locationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + minTimeMs, + minDistanceMeters, + continuousListener!! + ) + requested = true + } catch (e: Exception) { + LogUtils.e("GPS_STATUS", "Failed NETWORK request: ${e.message}") + } + } else if (hasCoarse) { + LogUtils.w("GPS_STATUS", "Network provider disabled; skip NETWORK request") + } + + if (requested) { + LogUtils.d("GPS_STATUS", "Started continuous updates (minTimeMs=$minTimeMs, minDistanceMeters=$minDistanceMeters)") + } else { + LogUtils.e("GPS_STATUS", "No provider requested (permissions/providers)") + } } fun startContinuousMode() { stop() - startContinuousUpdates() + currentMode = GpsTrackingMode.DRIVING + startContinuousUpdates(0L, 0f) } fun startPollingMode() { stop() - isPolling = true - handler.post(pollingRunnable) + currentMode = GpsTrackingMode.NORMAL + startContinuousUpdates(pollingIntervalMs, minDistanceMeters) } fun stop() { - isPolling = false - handler.removeCallbacks(pollingRunnable) - locationManager.unregisterGnssStatusCallback(gnssStatusCallback) + try { + locationManager.unregisterGnssStatusCallback(gnssStatusCallback) + } catch (_: Exception) {} continuousListener?.let { locationManager.removeUpdates(it) continuousListener = null diff --git a/android/src/main/java/com/dumon/plugin/geolocation/imu/ImuSensorManager.kt b/android/src/main/java/com/dumon/plugin/geolocation/imu/ImuSensorManager.kt index ae47971..05c490b 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/imu/ImuSensorManager.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/imu/ImuSensorManager.kt @@ -29,6 +29,7 @@ class ImuSensorManager( private var latestDirectionRad = 0f private var latestSpeed = 0f + private var lastLogTimestampMs: Long = 0L private var currentDelay: Int = SensorManager.SENSOR_DELAY_GAME @@ -111,9 +112,16 @@ class ImuSensorManager( emitCombinedImuData(speed, acceleration, latestDirectionRad) - LogUtils.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format( - lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad - )) + val nowMs = System.currentTimeMillis() + if (nowMs - lastLogTimestampMs >= 1000L) { + lastLogTimestampMs = nowMs + LogUtils.d( + "IMU_SENSOR", + "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format( + lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad + ) + ) + } } private fun handleGyroscope(event: SensorEvent) { diff --git a/android/src/main/java/com/dumon/plugin/geolocation/utils/BgPrefs.kt b/android/src/main/java/com/dumon/plugin/geolocation/utils/BgPrefs.kt index f0af71c..11bc824 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/utils/BgPrefs.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/utils/BgPrefs.kt @@ -57,11 +57,6 @@ object BgPrefs { .putLong(KEY_TS, timestamp) .putString(KEY_SRC, source) .putBoolean(KEY_MOCK, isMocked) - .apply() - - // Optional IMU fields - context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - .edit() .putFloat(KEY_SPEED, speed ?: 0f) .putFloat(KEY_ACCEL, acceleration ?: 0f) .putFloat(KEY_DIR, directionRad ?: 0f) diff --git a/android/src/main/java/com/dumon/plugin/geolocation/utils/PermissionUtils.kt b/android/src/main/java/com/dumon/plugin/geolocation/utils/PermissionUtils.kt index 844d980..a1873db 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/utils/PermissionUtils.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/utils/PermissionUtils.kt @@ -7,21 +7,31 @@ import android.os.Build import androidx.core.app.ActivityCompat object PermissionUtils { - fun hasLocationAndWifiPermissions(context: Context): Boolean { + fun hasLocationPermissions(context: Context): Boolean { val fineLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) val coarseLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) - val nearbyWifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) - } else { - PackageManager.PERMISSION_GRANTED + return fineLocation == PackageManager.PERMISSION_GRANTED || coarseLocation == PackageManager.PERMISSION_GRANTED + } + + fun hasWifiScanPermissions(context: Context): Boolean { + val wifiState = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_WIFI_STATE) + val changeWifi = ActivityCompat.checkSelfPermission(context, Manifest.permission.CHANGE_WIFI_STATE) + if (wifiState != PackageManager.PERMISSION_GRANTED || changeWifi != PackageManager.PERMISSION_GRANTED) { + return false } - return fineLocation == PackageManager.PERMISSION_GRANTED && - coarseLocation == PackageManager.PERMISSION_GRANTED && - nearbyWifi == PackageManager.PERMISSION_GRANTED + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) == PackageManager.PERMISSION_GRANTED + } else { + hasLocationPermissions(context) + } + } + + fun hasLocationAndWifiPermissions(context: Context): Boolean { + return hasLocationPermissions(context) && hasWifiScanPermissions(context) } fun getPermissionStatus(granted: Int): String { return if (granted == PackageManager.PERMISSION_GRANTED) "granted" else "denied" } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/dumon/plugin/geolocation/wifi/WifiPositioningManager.kt b/android/src/main/java/com/dumon/plugin/geolocation/wifi/WifiPositioningManager.kt index 8928274..3e34e03 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/wifi/WifiPositioningManager.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/wifi/WifiPositioningManager.kt @@ -14,8 +14,8 @@ import android.net.wifi.rtt.WifiRttManager import android.os.* import android.util.Log import com.dumon.plugin.geolocation.utils.LogUtils -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import com.dumon.plugin.geolocation.utils.PermissionUtils class WifiPositioningManager( private val context: Context, @@ -62,18 +62,20 @@ class WifiPositioningManager( } fun startWifiScan() { - if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION - ) == PackageManager.PERMISSION_GRANTED - ) { + if (!PermissionUtils.hasWifiScanPermissions(context)) { + LogUtils.e("WIFI_POSITION", "Missing Wi-Fi scan permission") + onWifiPositioningUpdate(WifiScanResult(0, emptyList())) + return + } + + try { val success = wifiManager.startScan() if (!success) { LogUtils.e("WIFI_POSITION", "Wi-Fi scan failed") onWifiPositioningUpdate(WifiScanResult(0, emptyList())) } - } else { - LogUtils.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission") + } catch (e: SecurityException) { + LogUtils.e("WIFI_POSITION", "SecurityException on Wi-Fi scan: ${e.message}") onWifiPositioningUpdate(WifiScanResult(0, emptyList())) } } diff --git a/dist/esm/definitions.js.map b/dist/esm/definitions.js.map index 9de2d5b..46ac2cb 100644 --- a/dist/esm/definitions.js.map +++ b/dist/esm/definitions.js.map @@ -1 +1 @@ -{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n mode: 'normal' | 'driving';\n predicted?: boolean;\n // Optional detailed speed fields and provenance\n speedSource?: 'GNSS' | 'IMU' | 'DELTA' | 'NONE';\n speedGnss?: number; // m/s from Location.getSpeed when available and fresh\n speedImu?: number; // m/s from IMU fusion (internal heuristic)\n speedDerived?: number; // m/s from delta-position / delta-time (coarse)\n}\n\nexport interface SatelliteStatus {\n satellitesInView: number;\n usedInFix: number;\n constellationCounts: { [key: string]: number };\n}\n\nexport interface DumonGeoOptions {\n distanceThresholdMeters?: number;\n speedChangeThreshold?: number;\n directionChangeThreshold?: number;\n emitDebounceMs?: number;\n drivingEmitIntervalMs?: number;\n wifiScanIntervalMs?: number;\n enableWifiRtt?: boolean;\n enableLogging?: boolean;\n enableForwardPrediction?: boolean;\n maxPredictionSeconds?: number;\n emitGnssStatus?: boolean;\n suppressMockedUpdates?: boolean;\n keepScreenOn?: boolean;\n backgroundPollingIntervalMs?: number; // Android background polling interval\n backgroundPostMinDistanceMeters?: number; // Android background min distance to post\n backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)\n backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts\n backgroundUseImuFallback?: boolean; // Android background: enable IMU-based speed fallback (default true)\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n setOptions(options: DumonGeoOptions): Promise;\n getGnssStatus(): Promise;\n getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;\n // Background tracking (Android)\n startBackgroundTracking(options?: {\n title?: string;\n text?: string;\n channelId?: string;\n channelName?: string;\n postUrl?: string; // optional: service will POST latest fixes here as JSON\n }): Promise;\n stopBackgroundTracking(): Promise;\n isBackgroundTrackingActive(): Promise<{ active: boolean }>;\n getBackgroundLatestPosition(): Promise;\n openBackgroundPermissionSettings(): Promise;\n openNotificationPermissionSettings(): Promise;\n // Auth token management for background posting\n setAuthTokens(tokens: { accessToken: string; refreshToken: string }): Promise;\n clearAuthTokens(): Promise;\n getAuthState(): Promise<{ present: boolean }>;\n setBackgroundPostUrl(options: { url?: string }): Promise;\n getBackgroundPostUrl(): Promise<{ url: string | null }>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n\n addListener(\n eventName: 'onGnssStatus',\n listenerFunc: (data: SatelliteStatus) => void\n ): PluginListenerHandle;\n}\n"]} \ No newline at end of file +{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n mode: 'normal' | 'driving';\n predicted?: boolean;\n // Optional detailed speed fields and provenance\n speedSource?: 'GNSS' | 'IMU' | 'DELTA' | 'NONE';\n speedGnss?: number; // m/s from Location.getSpeed when available and fresh\n speedImu?: number; // m/s from IMU fusion (internal heuristic)\n speedDerived?: number; // m/s from delta-position / delta-time (coarse)\n}\n\nexport interface SatelliteStatus {\n satellitesInView: number;\n usedInFix: number;\n constellationCounts: { [key: string]: number };\n}\n\nexport interface DumonGeoOptions {\n distanceThresholdMeters?: number;\n speedChangeThreshold?: number;\n directionChangeThreshold?: number;\n emitDebounceMs?: number;\n drivingEmitIntervalMs?: number;\n wifiScanIntervalMs?: number;\n enableWifiRtt?: boolean;\n enableLogging?: boolean;\n enableForwardPrediction?: boolean;\n maxPredictionSeconds?: number;\n emitGnssStatus?: boolean;\n suppressMockedUpdates?: boolean;\n keepScreenOn?: boolean;\n backgroundPollingIntervalMs?: number; // Android background polling interval\n backgroundPostMinDistanceMeters?: number; // Android background min distance to post\n backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)\n backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts\n backgroundUseImuFallback?: boolean; // Android background: enable IMU-based speed fallback (default false)\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n setOptions(options: DumonGeoOptions): Promise;\n getGnssStatus(): Promise;\n getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;\n // Background tracking (Android)\n startBackgroundTracking(options?: {\n title?: string;\n text?: string;\n channelId?: string;\n channelName?: string;\n postUrl?: string; // optional: service will POST latest fixes here as JSON\n }): Promise;\n stopBackgroundTracking(): Promise;\n isBackgroundTrackingActive(): Promise<{ active: boolean }>;\n getBackgroundLatestPosition(): Promise;\n openBackgroundPermissionSettings(): Promise;\n openNotificationPermissionSettings(): Promise;\n // Auth token management for background posting\n setAuthTokens(tokens: { accessToken: string; refreshToken: string }): Promise;\n clearAuthTokens(): Promise;\n getAuthState(): Promise<{ present: boolean }>;\n setBackgroundPostUrl(options: { url?: string }): Promise;\n getBackgroundPostUrl(): Promise<{ url: string | null }>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n\n addListener(\n eventName: 'onGnssStatus',\n listenerFunc: (data: SatelliteStatus) => void\n ): PluginListenerHandle;\n}\n"]} \ No newline at end of file diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts index 69ebfd0..c6e805f 100644 --- a/dist/esm/index.d.ts +++ b/dist/esm/index.d.ts @@ -1,4 +1,8 @@ import type { DumonGeolocationPlugin } from './definitions'; -declare const DumonGeolocation: DumonGeolocationPlugin; +declare const DumonGeolocationPlugin: DumonGeolocationPlugin; +declare const DumonGeolocation: DumonGeolocationPlugin & { + getGnssStatus(): Promise; + getBackgroundLatestPosition(): Promise; +}; export * from './definitions'; export { DumonGeolocation }; diff --git a/dist/esm/index.js b/dist/esm/index.js index 671051d..8737078 100644 --- a/dist/esm/index.js +++ b/dist/esm/index.js @@ -1,7 +1,22 @@ import { registerPlugin } from '@capacitor/core'; -const DumonGeolocation = registerPlugin('DumonGeolocation', { +const DumonGeolocationPlugin = registerPlugin('DumonGeolocation', { web: () => import('./web').then((m) => new m.DumonGeolocationWeb()), }); +const isEmptyObject = (value) => { + if (!value || typeof value !== 'object' || Array.isArray(value)) + return false; + return Object.keys(value).length === 0; +}; +const DumonGeolocation = Object.assign(DumonGeolocationPlugin, { + async getGnssStatus() { + const result = await DumonGeolocationPlugin.getGnssStatus(); + return isEmptyObject(result) ? null : result; + }, + async getBackgroundLatestPosition() { + const result = await DumonGeolocationPlugin.getBackgroundLatestPosition(); + return isEmptyObject(result) ? null : result; + }, +}); export * from './definitions'; export { DumonGeolocation }; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/esm/index.js.map b/dist/esm/index.js.map index 2e33c7b..b1399c7 100644 --- a/dist/esm/index.js.map +++ b/dist/esm/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,gBAAgB,GAAG,cAAc,CAAyB,kBAAkB,EAAE;IAClF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CACpE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { DumonGeolocationPlugin } from './definitions';\n\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\n\nexport * from './definitions';\nexport { DumonGeolocation };"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,sBAAsB,GAAG,cAAc,CAAyB,kBAAkB,EAAE;IACxF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CACpE,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,OAAO,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE;IAC7D,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,aAAa,EAAE,CAAC;QAC5D,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,CAAC;IACD,KAAK,CAAC,2BAA2B;QAC/B,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,2BAA2B,EAAE,CAAC;QAC1E,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,CAAC;CACF,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { DumonGeolocationPlugin } from './definitions';\n\nconst DumonGeolocationPlugin = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\n\nconst isEmptyObject = (value: unknown): boolean => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n return Object.keys(value as Record).length === 0;\n};\n\nconst DumonGeolocation = Object.assign(DumonGeolocationPlugin, {\n async getGnssStatus() {\n const result = await DumonGeolocationPlugin.getGnssStatus();\n return isEmptyObject(result) ? null : result;\n },\n async getBackgroundLatestPosition() {\n const result = await DumonGeolocationPlugin.getBackgroundLatestPosition();\n return isEmptyObject(result) ? null : result;\n },\n});\n\nexport * from './definitions';\nexport { DumonGeolocation };\n"]} \ No newline at end of file diff --git a/dist/plugin.cjs.js b/dist/plugin.cjs.js deleted file mode 100644 index 8f72228..0000000 --- a/dist/plugin.cjs.js +++ /dev/null @@ -1,105 +0,0 @@ -'use strict'; - -var core = require('@capacitor/core'); - -const DumonGeolocation = core.registerPlugin('DumonGeolocation', { - web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DumonGeolocationWeb()), -}); - -class DumonGeolocationWeb extends core.WebPlugin { - constructor() { - super(...arguments); - this._mode = 'normal'; - } - async startPositioning() { - console.log('DumonGeolocationWeb: startPositioning() called (no-op)'); - } - async stopPositioning() { - console.log('DumonGeolocationWeb: stopPositioning() called (no-op)'); - } - async getLatestPosition() { - console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)'); - return { - source: 'GNSS', - timestamp: Date.now(), - latitude: 0, - longitude: 0, - accuracy: 999, - speed: 0, - acceleration: 0, - directionRad: 0, - isMocked: false, - mode: this._mode, - speedSource: 'NONE', - speedImu: 0, - speedGnss: 0, - speedDerived: 0, - }; - } - async checkAndRequestPermissions() { - console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.'); - return { - location: 'granted', - wifi: 'granted', - }; - } - async configureEdgeToEdge(options) { - console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); - // No-op - } - async setOptions(_options) { - // No-op on web - } - async setGpsMode(options) { - this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal'; - } - async getGnssStatus() { - return null; - } - async getLocationServicesStatus() { - // Web stub; assume enabled - return { gpsEnabled: true, networkEnabled: true }; - } - // Background tracking stubs (no-op on web) - async startBackgroundTracking(_options) { - console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.'); - } - async stopBackgroundTracking() { - console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.'); - } - async isBackgroundTrackingActive() { - return { active: false }; - } - async getBackgroundLatestPosition() { - return null; - } - async openBackgroundPermissionSettings() { - console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.'); - } - async openNotificationPermissionSettings() { - console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.'); - } - async setAuthTokens(_tokens) { - console.info('[dumon-geolocation] setAuthTokens is a no-op on web.'); - } - async clearAuthTokens() { - console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.'); - } - async getAuthState() { - return { present: false }; - } - async setBackgroundPostUrl(_options) { - console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.'); - } - async getBackgroundPostUrl() { - return { url: null }; - } -} - -var web = /*#__PURE__*/Object.freeze({ - __proto__: null, - DumonGeolocationWeb: DumonGeolocationWeb -}); - -exports.DumonGeolocation = DumonGeolocation; -//# sourceMappingURL=plugin.cjs.js.map diff --git a/dist/plugin.cjs.js.map b/dist/plugin.cjs.js.map deleted file mode 100644 index ce97e91..0000000 --- a/dist/plugin.cjs.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this._mode = 'normal';\n }\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n mode: this._mode,\n speedSource: 'NONE',\n speedImu: 0,\n speedGnss: 0,\n speedDerived: 0,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n async setOptions(_options) {\n // No-op on web\n }\n async setGpsMode(options) {\n this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal';\n }\n async getGnssStatus() {\n return null;\n }\n async getLocationServicesStatus() {\n // Web stub; assume enabled\n return { gpsEnabled: true, networkEnabled: true };\n }\n // Background tracking stubs (no-op on web)\n async startBackgroundTracking(_options) {\n console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.');\n }\n async stopBackgroundTracking() {\n console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.');\n }\n async isBackgroundTrackingActive() {\n return { active: false };\n }\n async getBackgroundLatestPosition() {\n return null;\n }\n async openBackgroundPermissionSettings() {\n console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.');\n }\n async openNotificationPermissionSettings() {\n console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.');\n }\n async setAuthTokens(_tokens) {\n console.info('[dumon-geolocation] setAuthTokens is a no-op on web.');\n }\n async clearAuthTokens() {\n console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.');\n }\n async getAuthState() {\n return { present: false };\n }\n async setBackgroundPostUrl(_options) {\n console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.');\n }\n async getBackgroundPostUrl() {\n return { url: null };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"} \ No newline at end of file diff --git a/dist/plugin.js b/dist/plugin.js deleted file mode 100644 index 056b3bb..0000000 --- a/dist/plugin.js +++ /dev/null @@ -1,108 +0,0 @@ -var capacitorDumonGeolocation = (function (exports, core) { - 'use strict'; - - const DumonGeolocation = core.registerPlugin('DumonGeolocation', { - web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DumonGeolocationWeb()), - }); - - class DumonGeolocationWeb extends core.WebPlugin { - constructor() { - super(...arguments); - this._mode = 'normal'; - } - async startPositioning() { - console.log('DumonGeolocationWeb: startPositioning() called (no-op)'); - } - async stopPositioning() { - console.log('DumonGeolocationWeb: stopPositioning() called (no-op)'); - } - async getLatestPosition() { - console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)'); - return { - source: 'GNSS', - timestamp: Date.now(), - latitude: 0, - longitude: 0, - accuracy: 999, - speed: 0, - acceleration: 0, - directionRad: 0, - isMocked: false, - mode: this._mode, - speedSource: 'NONE', - speedImu: 0, - speedGnss: 0, - speedDerived: 0, - }; - } - async checkAndRequestPermissions() { - console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.'); - return { - location: 'granted', - wifi: 'granted', - }; - } - async configureEdgeToEdge(options) { - console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); - // No-op - } - async setOptions(_options) { - // No-op on web - } - async setGpsMode(options) { - this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal'; - } - async getGnssStatus() { - return null; - } - async getLocationServicesStatus() { - // Web stub; assume enabled - return { gpsEnabled: true, networkEnabled: true }; - } - // Background tracking stubs (no-op on web) - async startBackgroundTracking(_options) { - console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.'); - } - async stopBackgroundTracking() { - console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.'); - } - async isBackgroundTrackingActive() { - return { active: false }; - } - async getBackgroundLatestPosition() { - return null; - } - async openBackgroundPermissionSettings() { - console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.'); - } - async openNotificationPermissionSettings() { - console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.'); - } - async setAuthTokens(_tokens) { - console.info('[dumon-geolocation] setAuthTokens is a no-op on web.'); - } - async clearAuthTokens() { - console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.'); - } - async getAuthState() { - return { present: false }; - } - async setBackgroundPostUrl(_options) { - console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.'); - } - async getBackgroundPostUrl() { - return { url: null }; - } - } - - var web = /*#__PURE__*/Object.freeze({ - __proto__: null, - DumonGeolocationWeb: DumonGeolocationWeb - }); - - exports.DumonGeolocation = DumonGeolocation; - - return exports; - -})({}, capacitorExports); -//# sourceMappingURL=plugin.js.map diff --git a/dist/plugin.js.map b/dist/plugin.js.map deleted file mode 100644 index b3bd020..0000000 --- a/dist/plugin.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this._mode = 'normal';\n }\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n mode: this._mode,\n speedSource: 'NONE',\n speedImu: 0,\n speedGnss: 0,\n speedDerived: 0,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n async setOptions(_options) {\n // No-op on web\n }\n async setGpsMode(options) {\n this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal';\n }\n async getGnssStatus() {\n return null;\n }\n async getLocationServicesStatus() {\n // Web stub; assume enabled\n return { gpsEnabled: true, networkEnabled: true };\n }\n // Background tracking stubs (no-op on web)\n async startBackgroundTracking(_options) {\n console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.');\n }\n async stopBackgroundTracking() {\n console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.');\n }\n async isBackgroundTrackingActive() {\n return { active: false };\n }\n async getBackgroundLatestPosition() {\n return null;\n }\n async openBackgroundPermissionSettings() {\n console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.');\n }\n async openNotificationPermissionSettings() {\n console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.');\n }\n async setAuthTokens(_tokens) {\n console.info('[dumon-geolocation] setAuthTokens is a no-op on web.');\n }\n async clearAuthTokens() {\n console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.');\n }\n async getAuthState() {\n return { present: false };\n }\n async setBackgroundPostUrl(_options) {\n console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.');\n }\n async getBackgroundPostUrl() {\n return { url: null };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,WAAW,GAAG;IAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;IAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,QAAQ;IAC7B;IACA,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;IAC7E;IACA,IAAI,MAAM,eAAe,GAAG;IAC5B,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;IAC5E;IACA,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC;IAC7F,QAAQ,OAAO;IACf,YAAY,MAAM,EAAE,MAAM;IAC1B,YAAY,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;IACjC,YAAY,QAAQ,EAAE,CAAC;IACvB,YAAY,SAAS,EAAE,CAAC;IACxB,YAAY,QAAQ,EAAE,GAAG;IACzB,YAAY,KAAK,EAAE,CAAC;IACpB,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,QAAQ,EAAE,KAAK;IAC3B,YAAY,IAAI,EAAE,IAAI,CAAC,KAAK;IAC5B,YAAY,WAAW,EAAE,MAAM;IAC/B,YAAY,QAAQ,EAAE,CAAC;IACvB,YAAY,SAAS,EAAE,CAAC;IACxB,YAAY,YAAY,EAAE,CAAC;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,0BAA0B,GAAG;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;IACtF,QAAQ,OAAO;IACf,YAAY,QAAQ,EAAE,SAAS;IAC/B,YAAY,IAAI,EAAE,SAAS;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,mBAAmB,CAAC,OAAO,EAAE;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC;IAC5F;IACA;IACA,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;IAC/B;IACA;IACA,IAAI,MAAM,UAAU,CAAC,OAAO,EAAE;IAC9B,QAAQ,IAAI,CAAC,KAAK,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ;IAC1H;IACA,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,OAAO,IAAI;IACnB;IACA,IAAI,MAAM,yBAAyB,GAAG;IACtC;IACA,QAAQ,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;IACzD;IACA;IACA,IAAI,MAAM,uBAAuB,CAAC,QAAQ,EAAE;IAC5C,QAAQ,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC;IAC5F;IACA,IAAI,MAAM,sBAAsB,GAAG;IACnC,QAAQ,OAAO,CAAC,IAAI,CAAC,qEAAqE,CAAC;IAC3F;IACA,IAAI,MAAM,0BAA0B,GAAG;IACvC,QAAQ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;IAChC;IACA,IAAI,MAAM,2BAA2B,GAAG;IACxC,QAAQ,OAAO,IAAI;IACnB;IACA,IAAI,MAAM,gCAAgC,GAAG;IAC7C,QAAQ,OAAO,CAAC,IAAI,CAAC,+EAA+E,CAAC;IACrG;IACA,IAAI,MAAM,kCAAkC,GAAG;IAC/C,QAAQ,OAAO,CAAC,IAAI,CAAC,iFAAiF,CAAC;IACvG;IACA,IAAI,MAAM,aAAa,CAAC,OAAO,EAAE;IACjC,QAAQ,OAAO,CAAC,IAAI,CAAC,sDAAsD,CAAC;IAC5E;IACA,IAAI,MAAM,eAAe,GAAG;IAC5B,QAAQ,OAAO,CAAC,IAAI,CAAC,wDAAwD,CAAC;IAC9E;IACA,IAAI,MAAM,YAAY,GAAG;IACzB,QAAQ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE;IACjC;IACA,IAAI,MAAM,oBAAoB,CAAC,QAAQ,EAAE;IACzC,QAAQ,OAAO,CAAC,IAAI,CAAC,mEAAmE,CAAC;IACzF;IACA,IAAI,MAAM,oBAAoB,GAAG;IACjC,QAAQ,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE;IAC5B;IACA;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 4367fe5..0f40e9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,25 @@ import { registerPlugin } from '@capacitor/core'; import type { DumonGeolocationPlugin } from './definitions'; -const DumonGeolocation = registerPlugin('DumonGeolocation', { +const DumonGeolocationPlugin = registerPlugin('DumonGeolocation', { web: () => import('./web').then((m) => new m.DumonGeolocationWeb()), }); +const isEmptyObject = (value: unknown): boolean => { + if (!value || typeof value !== 'object' || Array.isArray(value)) return false; + return Object.keys(value as Record).length === 0; +}; + +const DumonGeolocation = Object.assign(DumonGeolocationPlugin, { + async getGnssStatus() { + const result = await DumonGeolocationPlugin.getGnssStatus(); + return isEmptyObject(result) ? null : result; + }, + async getBackgroundLatestPosition() { + const result = await DumonGeolocationPlugin.getBackgroundLatestPosition(); + return isEmptyObject(result) ? null : result; + }, +}); + export * from './definitions'; -export { DumonGeolocation }; \ No newline at end of file +export { DumonGeolocation };