20260113-01
This commit is contained in:
parent
d9292c3fc7
commit
91dbbf8ce7
@ -125,6 +125,7 @@ setOptions(options: {
|
||||
emitGnssStatus?: boolean;
|
||||
suppressMockedUpdates?: boolean;
|
||||
keepScreenOn?: boolean;
|
||||
debuggingMode?: boolean;
|
||||
backgroundPollingIntervalMs?: number;
|
||||
backgroundPostMinDistanceMeters?: number;
|
||||
backgroundPostMinAccuracyMeters?: number;
|
||||
@ -173,11 +174,17 @@ startBackgroundTracking(options?: {
|
||||
stopBackgroundTracking(): Promise<void>
|
||||
isBackgroundTrackingActive(): Promise<{ active: boolean }>
|
||||
getBackgroundLatestPosition(): Promise<PositioningData | null>
|
||||
triggerBackgroundReport(options?: {
|
||||
postUrl?: string;
|
||||
timeoutMs?: number;
|
||||
useNetworkProvider?: boolean;
|
||||
}): Promise<{ posted: boolean; endpoint?: string; usedCached?: boolean }>
|
||||
openBackgroundPermissionSettings(): Promise<void>
|
||||
openNotificationPermissionSettings(): Promise<void>
|
||||
```
|
||||
|
||||
Catatan: pada iOS, `title/text/channelId/channelName` diabaikan; `postUrl` tetap didukung.
|
||||
`triggerBackgroundReport` hanya tersedia di Android untuk memicu POST lokasi sekali (untuk debugging/dev tool).
|
||||
|
||||
### Auth & Post URL (Android + iOS)
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
@ -32,6 +33,11 @@ import com.dumon.plugin.geolocation.utils.BgPrefs
|
||||
import kotlin.math.*
|
||||
import android.location.Location
|
||||
import com.dumon.plugin.geolocation.utils.AuthStore
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
@CapacitorPlugin(
|
||||
name = "DumonGeolocation",
|
||||
@ -107,6 +113,7 @@ class DumonGeolocation : Plugin() {
|
||||
private var emitGnssStatus = false
|
||||
private var suppressMockedUpdates = false
|
||||
private var keepScreenOn = false
|
||||
private var debuggingMode = false
|
||||
private var lastSingleFixRequestTs: Long = 0L
|
||||
private val minSingleFixIntervalMs: Long = 1000L
|
||||
private var pendingPermissionAlias: String? = null
|
||||
@ -121,7 +128,9 @@ class DumonGeolocation : Plugin() {
|
||||
satelliteStatus = status
|
||||
if (emitGnssStatus) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
notifyListeners("onGnssStatus", buildGnssStatusData(status))
|
||||
val data = buildGnssStatusData(status)
|
||||
debugLog("emit onGnssStatus", data)
|
||||
notifyListeners("onGnssStatus", data)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -401,6 +410,15 @@ class DumonGeolocation : Plugin() {
|
||||
keepScreenOn = it
|
||||
applyKeepScreenOn(keepScreenOn)
|
||||
}
|
||||
call.getBoolean("debuggingMode")?.let {
|
||||
if (it && !debuggingMode) {
|
||||
debuggingMode = true
|
||||
debugLog("debuggingMode enabled")
|
||||
} else if (!it && debuggingMode) {
|
||||
debugLog("debuggingMode disabled")
|
||||
debuggingMode = false
|
||||
}
|
||||
}
|
||||
call.getInt("backgroundPollingIntervalMs")?.let {
|
||||
val bgInterval = it.toLong().coerceAtLeast(1000L)
|
||||
BgPrefs.setBackgroundIntervalMs(context, bgInterval)
|
||||
@ -444,6 +462,80 @@ class DumonGeolocation : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun debugLog(message: String, data: JSObject? = null) {
|
||||
if (!debuggingMode) return
|
||||
val timestamp = System.currentTimeMillis()
|
||||
if (data != null) {
|
||||
Log.d("DUMON_GEO_DEBUG", "[$timestamp] $message payload=$data")
|
||||
} else {
|
||||
Log.d("DUMON_GEO_DEBUG", "[$timestamp] $message")
|
||||
}
|
||||
}
|
||||
|
||||
private fun postBackgroundFix(
|
||||
endpoint: String,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
accuracy: Double,
|
||||
speed: Double,
|
||||
timestamp: Long,
|
||||
source: String,
|
||||
isMocked: Boolean,
|
||||
usedCached: Boolean,
|
||||
call: PluginCall
|
||||
) {
|
||||
Thread {
|
||||
val json = """{"source":"$source","timestamp":$timestamp,"latitude":$latitude,"longitude":$longitude,"accuracy":$accuracy,"speed":$speed,"isMocked":$isMocked}"""
|
||||
try {
|
||||
val url = URL(endpoint)
|
||||
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "POST"
|
||||
connectTimeout = 5000
|
||||
readTimeout = 5000
|
||||
doOutput = true
|
||||
setRequestProperty("Content-Type", "application/json")
|
||||
val tokens = AuthStore.getTokens(context)
|
||||
if (tokens != null) {
|
||||
setRequestProperty("Authorization", "Bearer ${tokens.accessToken}")
|
||||
setRequestProperty("refresh-token", tokens.refreshToken)
|
||||
}
|
||||
}
|
||||
try {
|
||||
BufferedOutputStream(conn.outputStream).use { out ->
|
||||
out.write(json.toByteArray(Charsets.UTF_8))
|
||||
out.flush()
|
||||
}
|
||||
val code = conn.responseCode
|
||||
if (code in 200..299) {
|
||||
BgPrefs.setLastPostedFix(context, latitude, longitude, timestamp)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
call.resolve(
|
||||
JSObject().apply {
|
||||
put("posted", true)
|
||||
put("endpoint", endpoint)
|
||||
put("usedCached", usedCached)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val err = try {
|
||||
BufferedReader(InputStreamReader(conn.errorStream ?: conn.inputStream)).readText()
|
||||
} catch (_: Exception) { "" }
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
call.reject("Background report failed (HTTP $code) $err")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
conn.disconnect()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
call.reject("Background report failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun getGnssStatus(call: PluginCall) {
|
||||
val status = satelliteStatus
|
||||
@ -543,6 +635,84 @@ class DumonGeolocation : Plugin() {
|
||||
call.resolve(obj)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun triggerBackgroundReport(call: PluginCall) {
|
||||
if (!PermissionUtils.hasLocationPermissions(context)) {
|
||||
call.reject("Location permission not granted")
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val bgGranted = ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.ACCESS_BACKGROUND_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
if (!bgGranted) {
|
||||
call.reject("Background location permission not granted")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val endpoint = call.getString("postUrl") ?: BgPrefs.getPostUrl(context)
|
||||
if (endpoint.isNullOrBlank()) {
|
||||
call.reject("Background postUrl not set")
|
||||
return
|
||||
}
|
||||
|
||||
val timeoutMs = call.getInt("timeoutMs")?.toLong()?.coerceAtLeast(1000L) ?: 3000L
|
||||
val useNetwork = call.getBoolean("useNetworkProvider") ?: true
|
||||
val manager = gpsManager ?: GpsStatusManager(context)
|
||||
|
||||
manager.requestSingleFix(timeoutMs = timeoutMs, useNetworkProvider = useNetwork) { location, isMocked ->
|
||||
if (location != null) {
|
||||
BgPrefs.saveLatestFix(
|
||||
context = context,
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
accuracy = location.accuracy.toDouble(),
|
||||
timestamp = location.time,
|
||||
source = if (isMocked) "MOCK" else "GNSS",
|
||||
isMocked = isMocked,
|
||||
speed = if (location.hasSpeed()) location.speed else 0f,
|
||||
acceleration = null,
|
||||
directionRad = null
|
||||
)
|
||||
postBackgroundFix(
|
||||
endpoint = endpoint,
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
accuracy = location.accuracy.toDouble(),
|
||||
speed = if (location.hasSpeed()) location.speed.toDouble() else 0.0,
|
||||
timestamp = location.time,
|
||||
source = if (isMocked) "MOCK" else "GNSS",
|
||||
isMocked = isMocked,
|
||||
usedCached = false,
|
||||
call = call
|
||||
)
|
||||
return@requestSingleFix
|
||||
}
|
||||
|
||||
val cached = BgPrefs.readLatestFix(context)
|
||||
if (cached == null) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
call.reject("No location available for background report")
|
||||
}
|
||||
return@requestSingleFix
|
||||
}
|
||||
postBackgroundFix(
|
||||
endpoint = endpoint,
|
||||
latitude = cached.latitude,
|
||||
longitude = cached.longitude,
|
||||
accuracy = cached.accuracy,
|
||||
speed = cached.speed.toDouble(),
|
||||
timestamp = cached.timestamp,
|
||||
source = cached.source,
|
||||
isMocked = cached.isMocked,
|
||||
usedCached = true,
|
||||
call = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Auth token management for background posting ---
|
||||
@PluginMethod
|
||||
fun setAuthTokens(call: PluginCall) {
|
||||
@ -650,7 +820,9 @@ class DumonGeolocation : Plugin() {
|
||||
|
||||
// Ensure listener notifications run on the main thread for consistency
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
notifyListeners("onPositionUpdate", buildPositionData())
|
||||
val data = buildPositionData()
|
||||
debugLog("emit onPositionUpdate", data)
|
||||
notifyListeners("onPositionUpdate", data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ export interface DumonGeoOptions {
|
||||
emitGnssStatus?: boolean; // default false
|
||||
suppressMockedUpdates?: boolean; // default false
|
||||
keepScreenOn?: boolean; // default false
|
||||
debuggingMode?: boolean; // default false
|
||||
backgroundPollingIntervalMs?: number; // Android: interval polling service (default 5000)
|
||||
backgroundPostMinDistanceMeters?: number; // Android: minimum perpindahan untuk POST (default 10m)
|
||||
backgroundPostMinAccuracyMeters?: number; // Android: minimum akurasi fix untuk POST (meter); kosong = tidak dibatasi
|
||||
@ -124,6 +125,7 @@ startBackgroundTracking(options?: { title?: string; text?: string; channelId?: s
|
||||
stopBackgroundTracking(): Promise<void>
|
||||
isBackgroundTrackingActive(): Promise<{ active: boolean }>
|
||||
getBackgroundLatestPosition(): Promise<PositioningData | null>
|
||||
triggerBackgroundReport(options?: { postUrl?: string; timeoutMs?: number; useNetworkProvider?: boolean }): Promise<{ posted: boolean; endpoint?: string; usedCached?: boolean }>
|
||||
setAuthTokens(tokens: { accessToken: string; refreshToken: string }): Promise<void>
|
||||
clearAuthTokens(): Promise<void>
|
||||
getAuthState(): Promise<{ present: boolean }>
|
||||
@ -350,6 +352,9 @@ await DumonGeolocation.stopBackgroundTracking();
|
||||
await DumonGeolocation.setAuthTokens({ accessToken: '<ACCESS>', refreshToken: '<REFRESH>' });
|
||||
await DumonGeolocation.setBackgroundPostUrl({ url: 'https://dumonapp.com/dev-test-cap' });
|
||||
|
||||
// Debug/dev tool: paksa kirim satu report background (tanpa menunggu interval)
|
||||
await DumonGeolocation.triggerBackgroundReport({ postUrl: 'https://dumonapp.com/dev-test-cap' });
|
||||
|
||||
// Kirim otomatis hanya jika perpindahan >= 10 meter (default 10 m); atur interval polling 5 detik
|
||||
await DumonGeolocation.setOptions({ backgroundPostMinDistanceMeters: 10, backgroundPollingIntervalMs: 5000 });
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ import UIKit
|
||||
private var maxPredictionSeconds: Double = 1.0
|
||||
private var suppressMockedUpdates = false
|
||||
private var keepScreenOn = false
|
||||
private var debuggingMode = false
|
||||
private let debugDateFormatter = ISO8601DateFormatter()
|
||||
|
||||
private var currentMode: String = "normal"
|
||||
private var drivingTimer: Timer?
|
||||
@ -113,6 +115,15 @@ import UIKit
|
||||
keepScreenOn = v
|
||||
applyKeepScreenOn(v)
|
||||
}
|
||||
if let v = options["debuggingMode"] as? Bool {
|
||||
if v && !debuggingMode {
|
||||
debuggingMode = true
|
||||
debugLog("debuggingMode enabled")
|
||||
} else if !v && debuggingMode {
|
||||
debugLog("debuggingMode disabled")
|
||||
debuggingMode = false
|
||||
}
|
||||
}
|
||||
if let v = options["backgroundPostMinDistanceMeters"] as? Double {
|
||||
backgroundPostMinDistanceMeters = max(0.0, v)
|
||||
}
|
||||
@ -260,6 +271,7 @@ import UIKit
|
||||
|
||||
let data = buildPositionData(from: location)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.debugLog("emit onPositionUpdate", payload: data)
|
||||
self?.onPositionUpdate?(data)
|
||||
}
|
||||
}
|
||||
@ -415,4 +427,14 @@ import UIKit
|
||||
}
|
||||
}
|
||||
|
||||
private func debugLog(_ message: String, payload: [String: Any]? = nil) {
|
||||
guard debuggingMode else { return }
|
||||
let timestamp = debugDateFormatter.string(from: Date())
|
||||
if let payload = payload {
|
||||
NSLog("DUMON_GEO_DEBUG [\(timestamp)] \(message) payload=\(payload)")
|
||||
} else {
|
||||
NSLog("DUMON_GEO_DEBUG [\(timestamp)] \(message)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ public class DumonGeolocationPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
CAPPluginMethod(name: "stopBackgroundTracking", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "isBackgroundTrackingActive", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getBackgroundLatestPosition", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "triggerBackgroundReport", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "openBackgroundPermissionSettings", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "openNotificationPermissionSettings", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "setAuthTokens", returnType: CAPPluginReturnPromise),
|
||||
@ -135,6 +136,10 @@ public class DumonGeolocationPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func triggerBackgroundReport(_ call: CAPPluginCall) {
|
||||
call.reject("triggerBackgroundReport is Android-only")
|
||||
}
|
||||
|
||||
@objc func openBackgroundPermissionSettings(_ call: CAPPluginCall) {
|
||||
openAppSettings(call)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dumon-geolocation",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"description": "Implement manager GNSS, Wi‑Fi RTT, IMU, Kalman fusion, event emitter",
|
||||
"main": "dist/plugin.cjs.js",
|
||||
"module": "dist/esm/index.js",
|
||||
|
||||
@ -90,6 +90,7 @@ export interface DumonGeoOptions {
|
||||
emitGnssStatus?: boolean;
|
||||
suppressMockedUpdates?: boolean;
|
||||
keepScreenOn?: boolean;
|
||||
debuggingMode?: boolean;
|
||||
backgroundPollingIntervalMs?: number; // Android background polling interval
|
||||
backgroundPostMinDistanceMeters?: number; // Android background min distance to post
|
||||
backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)
|
||||
@ -121,6 +122,11 @@ export interface DumonGeolocationPlugin {
|
||||
stopBackgroundTracking(): Promise<void>;
|
||||
isBackgroundTrackingActive(): Promise<{ active: boolean }>;
|
||||
getBackgroundLatestPosition(): Promise<PositioningData | null>;
|
||||
triggerBackgroundReport(options?: {
|
||||
postUrl?: string;
|
||||
timeoutMs?: number;
|
||||
useNetworkProvider?: boolean;
|
||||
}): Promise<{ posted: boolean; endpoint?: string; usedCached?: boolean }>;
|
||||
openBackgroundPermissionSettings(): Promise<void>;
|
||||
openNotificationPermissionSettings(): Promise<void>;
|
||||
// Auth token management for background posting
|
||||
|
||||
@ -91,6 +91,15 @@ export class DumonGeolocationWeb extends WebPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
async triggerBackgroundReport(_options?: {
|
||||
postUrl?: string;
|
||||
timeoutMs?: number;
|
||||
useNetworkProvider?: boolean;
|
||||
}): Promise<{ posted: boolean; endpoint?: string; usedCached?: boolean }> {
|
||||
console.info('[dumon-geolocation] triggerBackgroundReport is not supported on web.');
|
||||
return { posted: false };
|
||||
}
|
||||
|
||||
async openBackgroundPermissionSettings(): Promise<void> {
|
||||
console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user