@bricks-soft/capacitor-location-tracking 0.0.0-stage → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2759 -2
- package/android/build.gradle +106 -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 +59 -4
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
// Owned by Unit 1 (TS wrapper + web).
|
|
6
|
+
// Capacitor calls the web factory again for every call made before the first one resolves, so the (stateful) web
|
|
7
|
+
// implementation is memoized: concurrent first calls such as `onLocation(...)` + `ready(...)` share one instance.
|
|
8
|
+
let webImplementation;
|
|
9
|
+
/** The Capacitor plugin object. */
|
|
10
|
+
const LocationTracking = core.registerPlugin('LocationTracking', {
|
|
11
|
+
web: () => webImplementation || (webImplementation = Promise.resolve().then(function () { return web; }).then((m) => new m.LocationTrackingWeb())),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/** Names of every event emitted by the plugin. */
|
|
15
|
+
const Events = {
|
|
16
|
+
LOCATION: 'location',
|
|
17
|
+
MOTIONCHANGE: 'motionchange',
|
|
18
|
+
ACTIVITYCHANGE: 'activitychange',
|
|
19
|
+
PROVIDERCHANGE: 'providerchange',
|
|
20
|
+
HEARTBEAT: 'heartbeat',
|
|
21
|
+
GEOFENCE: 'geofence',
|
|
22
|
+
GEOFENCESCHANGE: 'geofenceschange',
|
|
23
|
+
HTTP: 'http',
|
|
24
|
+
CONNECTIVITYCHANGE: 'connectivitychange',
|
|
25
|
+
POWERSAVECHANGE: 'powersavechange',
|
|
26
|
+
ENABLEDCHANGE: 'enabledchange',
|
|
27
|
+
NOTIFICATIONACTION: 'notificationaction',
|
|
28
|
+
AUTHORIZATION: 'authorization',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Subscribes to any plugin event with a typed payload. Call `remove()` on the resolved handle to unsubscribe.
|
|
32
|
+
* The helpers below are standalone functions on purpose: Capacitor's plugin proxy turns unknown properties of
|
|
33
|
+
* `LocationTracking` into native calls, so nothing is attached to it.
|
|
34
|
+
*/
|
|
35
|
+
function on(name, callback) {
|
|
36
|
+
return LocationTracking.addListener(name, callback);
|
|
37
|
+
}
|
|
38
|
+
/** Every recorded location, including persisted `current_position` / `watch_position` fixes and motion changes. */
|
|
39
|
+
const onLocation = (cb) => on('location', cb);
|
|
40
|
+
/** Moving / stationary transitions, with the fix at the moment of the change. */
|
|
41
|
+
const onMotionChange = (cb) => on('motionchange', cb);
|
|
42
|
+
/** Detected activity (still, walking, in_vehicle, ...). */
|
|
43
|
+
const onActivityChange = (cb) => on('activitychange', cb);
|
|
44
|
+
/** Location services or permission changes. */
|
|
45
|
+
const onProviderChange = (cb) => on('providerchange', cb);
|
|
46
|
+
/** Heartbeat records, created when no record was made for `heartbeat.minInterval` seconds while tracking. */
|
|
47
|
+
const onHeartbeat = (cb) => on('heartbeat', cb);
|
|
48
|
+
/** Geofence ENTER / EXIT / DWELL transitions. */
|
|
49
|
+
const onGeofence = (cb) => on('geofence', cb);
|
|
50
|
+
/** Geofences added to or removed from monitoring. */
|
|
51
|
+
const onGeofencesChange = (cb) => on('geofenceschange', cb);
|
|
52
|
+
/** Result of every HTTP upload request. */
|
|
53
|
+
const onHttp = (cb) => on('http', cb);
|
|
54
|
+
/** Network connectivity changes. */
|
|
55
|
+
const onConnectivityChange = (cb) => on('connectivitychange', cb);
|
|
56
|
+
/** Battery-saver mode changes. */
|
|
57
|
+
const onPowerSaveChange = (cb) => on('powersavechange', cb);
|
|
58
|
+
/** Tracking enabled / disabled. */
|
|
59
|
+
const onEnabledChange = (cb) => on('enabledchange', cb);
|
|
60
|
+
/** Taps on the foreground notification's action buttons. */
|
|
61
|
+
const onNotificationAction = (cb) => on('notificationaction', cb);
|
|
62
|
+
/** JWT refresh results. */
|
|
63
|
+
const onAuthorization = (cb) => on('authorization', cb);
|
|
64
|
+
|
|
65
|
+
/** Never copied: assigning `__proto__` would change the target's prototype instead of adding a key. */
|
|
66
|
+
const PROTO_KEY = '__proto__';
|
|
67
|
+
/** Free-form maps. They are config values, not sections: a new value replaces the old one instead of merging. */
|
|
68
|
+
const MAP_VALUES = [
|
|
69
|
+
'http.headers',
|
|
70
|
+
'http.params',
|
|
71
|
+
'persistence.extras',
|
|
72
|
+
'http.authorization.refreshPayload',
|
|
73
|
+
'http.authorization.refreshHeaders',
|
|
74
|
+
];
|
|
75
|
+
/** Sections whose own default is `null`. `null` resets them to `null`; setting them fills in their field defaults. */
|
|
76
|
+
const NULLABLE_SECTIONS = ['http.authorization'];
|
|
77
|
+
/**
|
|
78
|
+
* Every documented `@default` of {@link Config}, mirroring the Android `Config` model.
|
|
79
|
+
* Keys without a default (for example `notification.title`, which falls back to the app label) are left out.
|
|
80
|
+
*/
|
|
81
|
+
function defaultConfig() {
|
|
82
|
+
return {
|
|
83
|
+
geolocation: {
|
|
84
|
+
desiredAccuracy: 'high',
|
|
85
|
+
distanceFilter: 10,
|
|
86
|
+
locationUpdateInterval: 1000,
|
|
87
|
+
fastestLocationUpdateInterval: 500,
|
|
88
|
+
disableElasticity: false,
|
|
89
|
+
elasticityMultiplier: 1,
|
|
90
|
+
stationaryRadius: 25,
|
|
91
|
+
stopTimeout: 5,
|
|
92
|
+
stopAfterElapsedMinutes: 0,
|
|
93
|
+
stopOnStationary: false,
|
|
94
|
+
locationTimeout: 30000,
|
|
95
|
+
filter: {
|
|
96
|
+
useKalman: false,
|
|
97
|
+
trackingAccuracyThreshold: 100,
|
|
98
|
+
maxImpliedSpeed: 80,
|
|
99
|
+
odometerAccuracyThreshold: 20,
|
|
100
|
+
allowIdenticalLocations: false,
|
|
101
|
+
rejectMockLocations: false,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
activity: {
|
|
105
|
+
disableMotionActivityUpdates: false,
|
|
106
|
+
activityRecognitionInterval: 10000,
|
|
107
|
+
minimumActivityRecognitionConfidence: 75,
|
|
108
|
+
motionTriggerDelay: 0,
|
|
109
|
+
disableStopDetection: false,
|
|
110
|
+
},
|
|
111
|
+
heartbeat: {
|
|
112
|
+
enabled: true,
|
|
113
|
+
minInterval: 180,
|
|
114
|
+
maxInterval: 300,
|
|
115
|
+
},
|
|
116
|
+
http: {
|
|
117
|
+
url: null,
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {},
|
|
120
|
+
params: {},
|
|
121
|
+
autoSync: true,
|
|
122
|
+
autoSyncThreshold: 0,
|
|
123
|
+
syncInterval: 0,
|
|
124
|
+
batchSync: false,
|
|
125
|
+
maxBatchSize: 100,
|
|
126
|
+
disableAutoSyncOnCellular: false,
|
|
127
|
+
rootProperty: 'location',
|
|
128
|
+
locationTemplate: null,
|
|
129
|
+
geofenceTemplate: null,
|
|
130
|
+
timeout: 60000,
|
|
131
|
+
authorization: null,
|
|
132
|
+
},
|
|
133
|
+
persistence: {
|
|
134
|
+
maxDaysToPersist: 7,
|
|
135
|
+
maxRecordsToPersist: -1,
|
|
136
|
+
extras: {},
|
|
137
|
+
},
|
|
138
|
+
app: {
|
|
139
|
+
stopOnTerminate: true,
|
|
140
|
+
startOnBoot: false,
|
|
141
|
+
},
|
|
142
|
+
notification: {
|
|
143
|
+
text: 'Location tracking is active',
|
|
144
|
+
smallIcon: 'drawable/lt_ic_notification',
|
|
145
|
+
priority: 'default',
|
|
146
|
+
channelId: 'location_tracking',
|
|
147
|
+
channelName: 'Location tracking',
|
|
148
|
+
actions: [],
|
|
149
|
+
resume: {
|
|
150
|
+
enabled: false,
|
|
151
|
+
text: 'Location tracking is paused. Tap to resume.',
|
|
152
|
+
channelName: 'Paused location tracking',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
geofence: {
|
|
156
|
+
initialTriggerEntry: true,
|
|
157
|
+
},
|
|
158
|
+
logger: {
|
|
159
|
+
logLevel: 'info',
|
|
160
|
+
logMaxDays: 3,
|
|
161
|
+
},
|
|
162
|
+
backgroundPermissionRationale: {},
|
|
163
|
+
locationProvider: 'auto',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** Field defaults of `http.authorization`, applied once it is set (the section itself defaults to `null`). */
|
|
167
|
+
function defaultAuthorization() {
|
|
168
|
+
return {
|
|
169
|
+
strategy: 'JWT',
|
|
170
|
+
refreshPayload: {},
|
|
171
|
+
refreshHeaders: {},
|
|
172
|
+
refreshPayloadEncoding: 'json',
|
|
173
|
+
expires: -1,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function defaultTrees() {
|
|
177
|
+
const sections = defaultConfig();
|
|
178
|
+
sections.http.authorization = defaultAuthorization();
|
|
179
|
+
return { values: defaultConfig(), sections };
|
|
180
|
+
}
|
|
181
|
+
function isPlainObject(value) {
|
|
182
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
183
|
+
}
|
|
184
|
+
/** Deep copy of JSON-like data (plain objects, arrays and primitives). */
|
|
185
|
+
function cloneJson(value) {
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
return value.map((item) => cloneJson(item));
|
|
188
|
+
}
|
|
189
|
+
if (isPlainObject(value)) {
|
|
190
|
+
const copy = {};
|
|
191
|
+
for (const key of Object.keys(value)) {
|
|
192
|
+
if (key !== PROTO_KEY) {
|
|
193
|
+
copy[key] = cloneJson(value[key]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return copy;
|
|
197
|
+
}
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
function lookup(tree, path) {
|
|
201
|
+
let node = tree;
|
|
202
|
+
for (const key of path) {
|
|
203
|
+
if (!isPlainObject(node)) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
node = node[key];
|
|
207
|
+
}
|
|
208
|
+
return node;
|
|
209
|
+
}
|
|
210
|
+
function hasValue(patch) {
|
|
211
|
+
return Object.keys(patch).some((key) => patch[key] !== undefined && patch[key] !== null);
|
|
212
|
+
}
|
|
213
|
+
function mergeInto(target, patch, path, trees) {
|
|
214
|
+
for (const key of Object.keys(patch)) {
|
|
215
|
+
const value = patch[key];
|
|
216
|
+
if (value === undefined || key === PROTO_KEY) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const childPath = path.concat(key);
|
|
220
|
+
const dotted = childPath.join('.');
|
|
221
|
+
const sectionDefaults = lookup(trees.sections, childPath);
|
|
222
|
+
const isSection = isPlainObject(sectionDefaults) && MAP_VALUES.indexOf(dotted) < 0;
|
|
223
|
+
if (value === null) {
|
|
224
|
+
// null resets the key to its default (fields of a null section use the section's field defaults);
|
|
225
|
+
// keys without a default are removed.
|
|
226
|
+
const own = lookup(trees.values, childPath);
|
|
227
|
+
const reset = own !== undefined ? own : sectionDefaults;
|
|
228
|
+
if (reset === undefined) {
|
|
229
|
+
delete target[key];
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
target[key] = cloneJson(reset);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (isSection) {
|
|
236
|
+
// A section only accepts an object; anything else is ignored so the section stays fully populated.
|
|
237
|
+
const current = target[key];
|
|
238
|
+
// A null section is only switched on by a patch that sets something.
|
|
239
|
+
const staysNull = NULLABLE_SECTIONS.indexOf(dotted) >= 0 && current === null && !hasValue(value);
|
|
240
|
+
if (isPlainObject(value) && !staysNull) {
|
|
241
|
+
const base = isPlainObject(current) ? current : cloneJson(sectionDefaults);
|
|
242
|
+
mergeInto(base, value, childPath, trees);
|
|
243
|
+
target[key] = base;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Arrays, maps and scalar values are replaced.
|
|
248
|
+
target[key] = cloneJson(value);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** The same clamps the Android config validator applies. */
|
|
253
|
+
function clamp(config) {
|
|
254
|
+
const heartbeat = config.heartbeat;
|
|
255
|
+
if (heartbeat) {
|
|
256
|
+
if (typeof heartbeat.minInterval === 'number' && heartbeat.minInterval < 60) {
|
|
257
|
+
heartbeat.minInterval = 60;
|
|
258
|
+
}
|
|
259
|
+
if (typeof heartbeat.minInterval === 'number' &&
|
|
260
|
+
typeof heartbeat.maxInterval === 'number' &&
|
|
261
|
+
heartbeat.maxInterval < heartbeat.minInterval) {
|
|
262
|
+
heartbeat.maxInterval = heartbeat.minInterval;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const activity = config.activity;
|
|
266
|
+
if (activity && typeof activity.minimumActivityRecognitionConfidence === 'number') {
|
|
267
|
+
activity.minimumActivityRecognitionConfidence = Math.min(100, Math.max(0, activity.minimumActivityRecognitionConfidence));
|
|
268
|
+
}
|
|
269
|
+
const http = config.http;
|
|
270
|
+
if (http && typeof http.maxBatchSize === 'number' && http.maxBatchSize < 1) {
|
|
271
|
+
http.maxBatchSize = 1;
|
|
272
|
+
}
|
|
273
|
+
if (http && typeof http.syncInterval === 'number' && http.syncInterval < 0) {
|
|
274
|
+
http.syncInterval = 0;
|
|
275
|
+
}
|
|
276
|
+
const notification = config.notification;
|
|
277
|
+
if (notification && Array.isArray(notification.actions) && notification.actions.length > 3) {
|
|
278
|
+
notification.actions = notification.actions.slice(0, 3);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Returns `base` deep-merged with `patch`, without changing either.
|
|
283
|
+
* - Sections are merged key by key; arrays, maps (`headers`, `params`, `extras`, ...) and scalars are replaced.
|
|
284
|
+
* - `null` resets a key to its default (`http.authorization` resets to `null`); `undefined` is ignored.
|
|
285
|
+
*/
|
|
286
|
+
function mergeConfig(base, patch) {
|
|
287
|
+
const result = cloneJson(base);
|
|
288
|
+
if (patch) {
|
|
289
|
+
mergeInto(result, patch, [], defaultTrees());
|
|
290
|
+
}
|
|
291
|
+
const config = result;
|
|
292
|
+
clamp(config);
|
|
293
|
+
return config;
|
|
294
|
+
}
|
|
295
|
+
/** The web `State`: tracking is never enabled on web, and nothing is recorded or persisted. */
|
|
296
|
+
function webState(config) {
|
|
297
|
+
return {
|
|
298
|
+
enabled: false,
|
|
299
|
+
trackingMode: 'location',
|
|
300
|
+
isMoving: false,
|
|
301
|
+
odometer: 0,
|
|
302
|
+
backend: 'web',
|
|
303
|
+
lastRecordAt: null,
|
|
304
|
+
config: cloneJson(config),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Keep in sync with `package.json` (a test checks this) and `PLUGIN_VERSION` in `android/build.gradle`. */
|
|
309
|
+
const PLUGIN_VERSION = '8.0.0';
|
|
310
|
+
/** Tokens after `Android x` that do not name a device ('K' is Chrome's reduced user agent). */
|
|
311
|
+
const NOT_A_MODEL = ['K', 'U', 'wv', 'Mobile', 'Tablet', 'HarmonyOS'];
|
|
312
|
+
/** The device model among the `;`-separated tokens that follow `Android x` in the user agent's comment. */
|
|
313
|
+
function androidModel(tokens) {
|
|
314
|
+
for (const token of tokens) {
|
|
315
|
+
const model = token.replace(/\s*Build\/.*$/, '').trim();
|
|
316
|
+
const skip = !model ||
|
|
317
|
+
NOT_A_MODEL.indexOf(model) >= 0 ||
|
|
318
|
+
/^[a-z]{2}(?:[-_][a-z]{2})?$/i.test(model) || // locale, e.g. en-us
|
|
319
|
+
/^(?:rv:|HMSCore)/.test(model);
|
|
320
|
+
if (!skip) {
|
|
321
|
+
return model;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
function parseOs(ua) {
|
|
327
|
+
let match = /\(([^)]*?)Android\s*([\d.]*)([^)]*)\)/.exec(ua);
|
|
328
|
+
if (match) {
|
|
329
|
+
return {
|
|
330
|
+
osVersion: match[2] ? `Android ${match[2]}` : 'Android',
|
|
331
|
+
manufacturer: 'unknown',
|
|
332
|
+
model: androidModel(match[3].split(';')),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
match = /(iPhone|iPad|iPod).*?OS (\d+(?:_\d+)*)/.exec(ua);
|
|
336
|
+
if (match) {
|
|
337
|
+
return { osVersion: `iOS ${match[2].replace(/_/g, '.')}`, manufacturer: 'Apple', model: match[1] };
|
|
338
|
+
}
|
|
339
|
+
match = /Mac OS X (\d+(?:[_.]\d+)*)/.exec(ua);
|
|
340
|
+
if (match) {
|
|
341
|
+
return { osVersion: `macOS ${match[1].replace(/_/g, '.')}`, manufacturer: 'Apple', model: 'Macintosh' };
|
|
342
|
+
}
|
|
343
|
+
match = /Windows NT (\d+(?:\.\d+)*)/.exec(ua);
|
|
344
|
+
if (match) {
|
|
345
|
+
return { osVersion: `Windows ${match[1]}`, manufacturer: 'unknown', model: undefined };
|
|
346
|
+
}
|
|
347
|
+
match = /CrOS \S+ (\d+(?:\.\d+)*)/.exec(ua);
|
|
348
|
+
if (match) {
|
|
349
|
+
return { osVersion: `ChromeOS ${match[1]}`, manufacturer: 'unknown', model: undefined };
|
|
350
|
+
}
|
|
351
|
+
if (/Linux/.test(ua)) {
|
|
352
|
+
return { osVersion: 'Linux', manufacturer: 'unknown', model: undefined };
|
|
353
|
+
}
|
|
354
|
+
return { osVersion: 'unknown', manufacturer: 'unknown', model: undefined };
|
|
355
|
+
}
|
|
356
|
+
/** Browser name and major version, e.g. `Chrome 120`. Order matters: many browsers also claim Chrome or Safari. */
|
|
357
|
+
function parseBrowser(ua) {
|
|
358
|
+
const browsers = [
|
|
359
|
+
['Edge', /Edg(?:e|A|iOS)?\/(\d+)/],
|
|
360
|
+
['Opera', /(?:OPR|Opera)\/(\d+)/],
|
|
361
|
+
['Samsung Internet', /SamsungBrowser\/(\d+)/],
|
|
362
|
+
['Huawei Browser', /HuaweiBrowser\/(\d+)/],
|
|
363
|
+
['Firefox', /(?:Firefox|FxiOS)\/(\d+)/],
|
|
364
|
+
['Chrome', /(?:Chrome|CriOS)\/(\d+)/],
|
|
365
|
+
['Safari', /Version\/(\d+)[\d.]*.*Safari\//],
|
|
366
|
+
['Node.js', /Node\.js\/(\d+)/],
|
|
367
|
+
];
|
|
368
|
+
for (const [name, pattern] of browsers) {
|
|
369
|
+
const match = pattern.exec(ua);
|
|
370
|
+
if (match) {
|
|
371
|
+
return `${name} ${match[1]}`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return 'unknown';
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Best-effort `DeviceInfo` for a browser, parsed from `navigator.userAgent`.
|
|
378
|
+
* `brand` is the browser, `model` the device when the user agent names one (else `navigator.platform`),
|
|
379
|
+
* `osVersion` includes the OS name, and `sdkInt` is -1 because there is no Android API level.
|
|
380
|
+
*/
|
|
381
|
+
function webDeviceInfo(userAgent, platform) {
|
|
382
|
+
const os = parseOs(userAgent);
|
|
383
|
+
return {
|
|
384
|
+
platform: 'web',
|
|
385
|
+
manufacturer: os.manufacturer,
|
|
386
|
+
model: os.model || platform || 'unknown',
|
|
387
|
+
brand: parseBrowser(userAgent),
|
|
388
|
+
osVersion: os.osVersion,
|
|
389
|
+
sdkInt: -1,
|
|
390
|
+
pluginVersion: PLUGIN_VERSION,
|
|
391
|
+
gmsAvailable: false,
|
|
392
|
+
hmsAvailable: false,
|
|
393
|
+
backend: 'web',
|
|
394
|
+
packagedProviders: [],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Owned by Unit 1 (TS wrapper + web).
|
|
399
|
+
/** A rejection shaped like the native bridge's: a `CapacitorException` whose `code` is an `ErrorCode`. */
|
|
400
|
+
function webError(code, message) {
|
|
401
|
+
return new core.CapacitorException(message, code);
|
|
402
|
+
}
|
|
403
|
+
/** `GeolocationPositionError.code`: 1 = PERMISSION_DENIED, 2 = POSITION_UNAVAILABLE, 3 = TIMEOUT. */
|
|
404
|
+
function positionErrorInfo(error) {
|
|
405
|
+
const raw = error && typeof error.code === 'number' ? error.code : 0;
|
|
406
|
+
const code = raw === 1 ? 'PERMISSION_DENIED' : raw === 3 ? 'TIMEOUT' : 'UNAVAILABLE';
|
|
407
|
+
const fallback = code === 'PERMISSION_DENIED'
|
|
408
|
+
? 'Location permission denied.'
|
|
409
|
+
: code === 'TIMEOUT'
|
|
410
|
+
? 'Timed out waiting for a location.'
|
|
411
|
+
: 'Location unavailable.';
|
|
412
|
+
const message = error && typeof error.message === 'string' && error.message ? error.message : fallback;
|
|
413
|
+
return { code, message };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Largest delay `setTimeout` accepts; longer delays would fire immediately. */
|
|
417
|
+
const MAX_TIMER_MS = 2147483647;
|
|
418
|
+
/** How long the permission probe waits for a fix once the user has answered the prompt. */
|
|
419
|
+
const PROBE_TIMEOUT_MS = 10000;
|
|
420
|
+
/** Overall limit for the permission probe: some browsers never call back when their prompt is dismissed. */
|
|
421
|
+
const PROBE_DEADLINE_MS = 60000;
|
|
422
|
+
/** Returns `navigator.geolocation`, or `undefined` when the browser has none. */
|
|
423
|
+
function browserGeolocation() {
|
|
424
|
+
return typeof navigator !== 'undefined' && navigator && navigator.geolocation ? navigator.geolocation : undefined;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Takes up to `samples` fixes with consecutive `getCurrentPosition` calls and resolves the most accurate one.
|
|
428
|
+
* The whole run shares one deadline (`timeout`). When the deadline passes, or a later call fails, it resolves the
|
|
429
|
+
* best fix taken so far; with no fix it rejects with `TIMEOUT`, `PERMISSION_DENIED` or `UNAVAILABLE`.
|
|
430
|
+
*/
|
|
431
|
+
function sampleBestPosition(geolocation, options) {
|
|
432
|
+
return new Promise((resolve, reject) => {
|
|
433
|
+
const deadline = Date.now() + options.timeout;
|
|
434
|
+
let best = null;
|
|
435
|
+
let taken = 0;
|
|
436
|
+
let done = false;
|
|
437
|
+
let timer;
|
|
438
|
+
const finish = (error) => {
|
|
439
|
+
if (done) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
done = true;
|
|
443
|
+
if (timer !== undefined) {
|
|
444
|
+
clearTimeout(timer);
|
|
445
|
+
}
|
|
446
|
+
if (best) {
|
|
447
|
+
resolve(best);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
reject(error || webError('TIMEOUT', `No location within ${options.timeout} ms.`));
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
const next = () => {
|
|
454
|
+
if (done) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
geolocation.getCurrentPosition((position) => {
|
|
459
|
+
if (done) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
taken++;
|
|
463
|
+
if (!best || position.coords.accuracy <= best.coords.accuracy) {
|
|
464
|
+
best = position;
|
|
465
|
+
}
|
|
466
|
+
if (taken >= options.samples) {
|
|
467
|
+
finish();
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
next();
|
|
471
|
+
}
|
|
472
|
+
}, (error) => {
|
|
473
|
+
const info = positionErrorInfo(error);
|
|
474
|
+
finish(webError(info.code, info.message));
|
|
475
|
+
}, {
|
|
476
|
+
enableHighAccuracy: options.enableHighAccuracy,
|
|
477
|
+
maximumAge: taken === 0 ? options.maximumAge : 0,
|
|
478
|
+
timeout: Math.max(0, deadline - Date.now()),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
finish(webError('UNAVAILABLE', e instanceof Error ? e.message : String(e)));
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
// The browser's own timeout does not run while its permission prompt is open; this deadline does.
|
|
486
|
+
if (options.timeout <= MAX_TIMER_MS) {
|
|
487
|
+
timer = setTimeout(() => finish(), options.timeout);
|
|
488
|
+
}
|
|
489
|
+
next();
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Asks for one fix so the browser shows its permission prompt, and waits for the user's answer.
|
|
494
|
+
* Resolves `'denied'` on PERMISSION_DENIED and `'granted'` otherwise: the browser reports TIMEOUT and
|
|
495
|
+
* POSITION_UNAVAILABLE only after permission was granted. Resolves `'prompt'` if nothing happens within a minute.
|
|
496
|
+
*/
|
|
497
|
+
function probeLocationPermission(geolocation) {
|
|
498
|
+
return new Promise((resolve) => {
|
|
499
|
+
let done = false;
|
|
500
|
+
const finish = (state) => {
|
|
501
|
+
if (!done) {
|
|
502
|
+
done = true;
|
|
503
|
+
clearTimeout(timer);
|
|
504
|
+
resolve(state);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const timer = setTimeout(() => finish('prompt'), PROBE_DEADLINE_MS);
|
|
508
|
+
try {
|
|
509
|
+
geolocation.getCurrentPosition(() => finish('granted'), (error) => finish(positionErrorInfo(error).code === 'PERMISSION_DENIED' ? 'denied' : 'granted'), { enableHighAccuracy: false, maximumAge: Infinity, timeout: PROBE_TIMEOUT_MS });
|
|
510
|
+
}
|
|
511
|
+
catch (_a) {
|
|
512
|
+
finish('prompt');
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** State of the browser's `geolocation` permission, or `undefined` when the Permissions API cannot tell. */
|
|
517
|
+
async function queryLocationPermission() {
|
|
518
|
+
const permissions = typeof navigator !== 'undefined' && navigator ? navigator.permissions : undefined;
|
|
519
|
+
if (!permissions || typeof permissions.query !== 'function') {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
const status = await permissions.query({ name: 'geolocation' });
|
|
524
|
+
const state = status.state;
|
|
525
|
+
return state === 'granted' || state === 'denied' || state === 'prompt' ? state : 'prompt';
|
|
526
|
+
}
|
|
527
|
+
catch (_a) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** A version 4 UUID: `crypto.randomUUID()`, else built from `crypto.getRandomValues()`, else from `Math.random()`. */
|
|
533
|
+
function uuid() {
|
|
534
|
+
const webCrypto = typeof crypto !== 'undefined' ? crypto : undefined;
|
|
535
|
+
if (webCrypto && typeof webCrypto.randomUUID === 'function') {
|
|
536
|
+
return webCrypto.randomUUID();
|
|
537
|
+
}
|
|
538
|
+
const bytes = new Uint8Array(16);
|
|
539
|
+
if (webCrypto && typeof webCrypto.getRandomValues === 'function') {
|
|
540
|
+
webCrypto.getRandomValues(bytes);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
544
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
548
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
549
|
+
let hex = '';
|
|
550
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
551
|
+
hex += (bytes[i] + 0x100).toString(16).slice(1);
|
|
552
|
+
}
|
|
553
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
554
|
+
}
|
|
555
|
+
/** ISO-8601 UTC with milliseconds (`yyyy-MM-ddTHH:mm:ss.SSSZ`). Invalid input falls back to now. */
|
|
556
|
+
function isoTime(epochMs) {
|
|
557
|
+
const ms = typeof epochMs === 'number' && isFinite(epochMs) ? Math.round(epochMs) : Date.now();
|
|
558
|
+
return new Date(ms).toISOString();
|
|
559
|
+
}
|
|
560
|
+
/** Monotonic milliseconds since the page's time origin: the closest web equivalent of `elapsedRealtime`. */
|
|
561
|
+
function elapsedMs() {
|
|
562
|
+
const hasClock = typeof performance !== 'undefined' && typeof performance.now === 'function';
|
|
563
|
+
return hasClock ? Math.round(performance.now()) : 0;
|
|
564
|
+
}
|
|
565
|
+
function numberOrNull(value) {
|
|
566
|
+
return typeof value === 'number' && isFinite(value) ? value : null;
|
|
567
|
+
}
|
|
568
|
+
/** Maps a browser fix to the plugin's `Location` record shape. `extras` is left out when empty. */
|
|
569
|
+
function toLocation(position, event, extras) {
|
|
570
|
+
const coords = position.coords;
|
|
571
|
+
const location = {
|
|
572
|
+
uuid: uuid(),
|
|
573
|
+
event,
|
|
574
|
+
timestamp: isoTime(position.timestamp),
|
|
575
|
+
recorded_at: isoTime(Date.now()),
|
|
576
|
+
elapsed_realtime_ms: elapsedMs(),
|
|
577
|
+
boot_count: -1,
|
|
578
|
+
is_moving: false,
|
|
579
|
+
odometer: 0,
|
|
580
|
+
mock: false,
|
|
581
|
+
coords: {
|
|
582
|
+
latitude: coords.latitude,
|
|
583
|
+
longitude: coords.longitude,
|
|
584
|
+
accuracy: coords.accuracy,
|
|
585
|
+
altitude: numberOrNull(coords.altitude),
|
|
586
|
+
altitude_accuracy: numberOrNull(coords.altitudeAccuracy),
|
|
587
|
+
speed: numberOrNull(coords.speed),
|
|
588
|
+
speed_accuracy: null,
|
|
589
|
+
heading: numberOrNull(coords.heading),
|
|
590
|
+
heading_accuracy: null,
|
|
591
|
+
},
|
|
592
|
+
activity: { type: 'unknown', confidence: 0 },
|
|
593
|
+
battery: { level: -1, is_charging: false },
|
|
594
|
+
backend: 'web',
|
|
595
|
+
};
|
|
596
|
+
if (extras && Object.keys(extras).length > 0) {
|
|
597
|
+
location.extras = extras;
|
|
598
|
+
}
|
|
599
|
+
return location;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Owned by Unit 1 (TS wrapper + web).
|
|
603
|
+
const ALL_PERMISSIONS = ['location', 'notifications', 'activityRecognition', 'backgroundLocation'];
|
|
604
|
+
function highAccuracy(accuracy) {
|
|
605
|
+
return accuracy === undefined || accuracy === 'high';
|
|
606
|
+
}
|
|
607
|
+
function nonNegative(value, fallback) {
|
|
608
|
+
return typeof value === 'number' && value >= 0 ? value : fallback;
|
|
609
|
+
}
|
|
610
|
+
/** The defaults plus `options.config` (optional; must be an object when given). */
|
|
611
|
+
function configFromDefaults(options) {
|
|
612
|
+
const given = options ? options.config : undefined;
|
|
613
|
+
if (given !== undefined && given !== null && !isPlainObject(given)) {
|
|
614
|
+
throw webError('INVALID_ARGUMENT', 'config must be an object.');
|
|
615
|
+
}
|
|
616
|
+
return mergeConfig(defaultConfig(), given || undefined);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Web implementation. It covers `getCurrentPosition`, `watchPosition` and `clearWatch` (via `navigator.geolocation`),
|
|
620
|
+
* an in-memory config and state, permissions and device info. Nothing is persisted or uploaded, and tracking cannot
|
|
621
|
+
* be started. Every other method rejects with `UNIMPLEMENTED`.
|
|
622
|
+
*
|
|
623
|
+
* Like the Android bridge, methods other than `ready`, `getState`, `checkPermissions`, `requestPermissions` and
|
|
624
|
+
* `getDeviceInfo` reject with `NOT_READY` until `ready()` has resolved.
|
|
625
|
+
*/
|
|
626
|
+
class LocationTrackingWeb extends core.WebPlugin {
|
|
627
|
+
constructor() {
|
|
628
|
+
super(...arguments);
|
|
629
|
+
this.config = defaultConfig();
|
|
630
|
+
this.isReady = false;
|
|
631
|
+
this.lastPosition = null;
|
|
632
|
+
this.watchSeq = 0;
|
|
633
|
+
this.watches = new Map();
|
|
634
|
+
}
|
|
635
|
+
async ready(options) {
|
|
636
|
+
const config = configFromDefaults(options);
|
|
637
|
+
// reset:false applies the given config only on the first ready(); afterwards the current config wins.
|
|
638
|
+
if (!options || options.reset !== false || !this.isReady) {
|
|
639
|
+
this.config = config;
|
|
640
|
+
}
|
|
641
|
+
this.isReady = true;
|
|
642
|
+
return webState(this.config);
|
|
643
|
+
}
|
|
644
|
+
async setConfig(options) {
|
|
645
|
+
this.requireReady();
|
|
646
|
+
if (!options || !isPlainObject(options.config)) {
|
|
647
|
+
throw webError('INVALID_ARGUMENT', 'config must be an object.');
|
|
648
|
+
}
|
|
649
|
+
this.config = mergeConfig(this.config, options.config);
|
|
650
|
+
return webState(this.config);
|
|
651
|
+
}
|
|
652
|
+
async reset(options) {
|
|
653
|
+
this.requireReady();
|
|
654
|
+
this.config = configFromDefaults(options);
|
|
655
|
+
return webState(this.config);
|
|
656
|
+
}
|
|
657
|
+
async getState() {
|
|
658
|
+
return webState(this.config);
|
|
659
|
+
}
|
|
660
|
+
async start() {
|
|
661
|
+
throw this.unimplemented('Not implemented on web.');
|
|
662
|
+
}
|
|
663
|
+
async startGeofences() {
|
|
664
|
+
throw this.unimplemented('Not implemented on web.');
|
|
665
|
+
}
|
|
666
|
+
async stop() {
|
|
667
|
+
throw this.unimplemented('Not implemented on web.');
|
|
668
|
+
}
|
|
669
|
+
async changePace(_options) {
|
|
670
|
+
throw this.unimplemented('Not implemented on web.');
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Takes `samples` fixes within `timeout` and resolves the most accurate one. With `maximumAge > 0`, a fix this
|
|
674
|
+
* plugin received at most that long ago is returned right away. Like a persisted record on Android, the result is
|
|
675
|
+
* also emitted as a `location` event unless `persist` is false.
|
|
676
|
+
*/
|
|
677
|
+
async getCurrentPosition(options) {
|
|
678
|
+
var _a;
|
|
679
|
+
this.requireReady();
|
|
680
|
+
const geolocation = this.requireGeolocation();
|
|
681
|
+
const o = options || {};
|
|
682
|
+
const maximumAge = nonNegative(o.maximumAge, 0);
|
|
683
|
+
const cached = this.lastPosition;
|
|
684
|
+
let position;
|
|
685
|
+
if (maximumAge > 0 && cached && Date.now() - cached.timestamp <= maximumAge) {
|
|
686
|
+
position = cached;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
const samples = typeof o.samples === 'number' && o.samples >= 1 ? Math.floor(o.samples) : 3;
|
|
690
|
+
position = await sampleBestPosition(geolocation, {
|
|
691
|
+
samples,
|
|
692
|
+
timeout: nonNegative(o.timeout, nonNegative((_a = this.config.geolocation) === null || _a === void 0 ? void 0 : _a.locationTimeout, 30000)),
|
|
693
|
+
maximumAge,
|
|
694
|
+
enableHighAccuracy: highAccuracy(o.desiredAccuracy),
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
this.lastPosition = position;
|
|
698
|
+
const location = toLocation(position, 'current_position', this.recordExtras(o.extras));
|
|
699
|
+
if (o.persist !== false) {
|
|
700
|
+
this.emit('location', location);
|
|
701
|
+
}
|
|
702
|
+
return location;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Starts `navigator.geolocation.watchPosition` and resolves its id. The callback gets each fix as a
|
|
706
|
+
* `watch_position` Location, or `(null, { code, message })` on error. `interval` is ignored: the browser decides
|
|
707
|
+
* how often fixes arrive. With `persist: true` each fix is also emitted as a `location` event.
|
|
708
|
+
*/
|
|
709
|
+
async watchPosition(options, callback) {
|
|
710
|
+
this.requireReady();
|
|
711
|
+
if (typeof callback !== 'function') {
|
|
712
|
+
throw webError('INVALID_ARGUMENT', 'watchPosition requires a callback.');
|
|
713
|
+
}
|
|
714
|
+
const geolocation = this.requireGeolocation();
|
|
715
|
+
const o = options || {};
|
|
716
|
+
const callExtras = cloneJson(o.extras);
|
|
717
|
+
const persist = o.persist === true;
|
|
718
|
+
const entry = { active: true, geolocation, browserId: 0 };
|
|
719
|
+
try {
|
|
720
|
+
entry.browserId = geolocation.watchPosition((position) => {
|
|
721
|
+
// Browsers may still deliver a queued fix after clearWatch.
|
|
722
|
+
if (!entry.active) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
this.lastPosition = position;
|
|
726
|
+
const location = toLocation(position, 'watch_position', this.recordExtras(callExtras));
|
|
727
|
+
if (persist) {
|
|
728
|
+
this.emit('location', location);
|
|
729
|
+
}
|
|
730
|
+
callback(location);
|
|
731
|
+
}, (error) => {
|
|
732
|
+
if (entry.active) {
|
|
733
|
+
callback(null, positionErrorInfo(error));
|
|
734
|
+
}
|
|
735
|
+
}, { enableHighAccuracy: highAccuracy(o.desiredAccuracy), maximumAge: 0 });
|
|
736
|
+
}
|
|
737
|
+
catch (e) {
|
|
738
|
+
throw webError('UNAVAILABLE', e instanceof Error ? e.message : String(e));
|
|
739
|
+
}
|
|
740
|
+
const id = `web-watch-${++this.watchSeq}`;
|
|
741
|
+
this.watches.set(id, entry);
|
|
742
|
+
return id;
|
|
743
|
+
}
|
|
744
|
+
/** Stops a watch. Unknown ids are ignored. */
|
|
745
|
+
async clearWatch(options) {
|
|
746
|
+
this.requireReady();
|
|
747
|
+
if (!options || typeof options.id !== 'string') {
|
|
748
|
+
throw webError('INVALID_ARGUMENT', 'id must be a string.');
|
|
749
|
+
}
|
|
750
|
+
const entry = this.watches.get(options.id);
|
|
751
|
+
if (!entry) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
this.watches.delete(options.id);
|
|
755
|
+
entry.active = false;
|
|
756
|
+
entry.geolocation.clearWatch(entry.browserId);
|
|
757
|
+
}
|
|
758
|
+
async getOdometer() {
|
|
759
|
+
throw this.unimplemented('Not implemented on web.');
|
|
760
|
+
}
|
|
761
|
+
async setOdometer(_options) {
|
|
762
|
+
throw this.unimplemented('Not implemented on web.');
|
|
763
|
+
}
|
|
764
|
+
async resetOdometer() {
|
|
765
|
+
throw this.unimplemented('Not implemented on web.');
|
|
766
|
+
}
|
|
767
|
+
async getLocations(_options) {
|
|
768
|
+
throw this.unimplemented('Not implemented on web.');
|
|
769
|
+
}
|
|
770
|
+
async getCount() {
|
|
771
|
+
throw this.unimplemented('Not implemented on web.');
|
|
772
|
+
}
|
|
773
|
+
async insertLocation(_options) {
|
|
774
|
+
throw this.unimplemented('Not implemented on web.');
|
|
775
|
+
}
|
|
776
|
+
async destroyLocations() {
|
|
777
|
+
throw this.unimplemented('Not implemented on web.');
|
|
778
|
+
}
|
|
779
|
+
async destroyLocation(_options) {
|
|
780
|
+
throw this.unimplemented('Not implemented on web.');
|
|
781
|
+
}
|
|
782
|
+
async sync() {
|
|
783
|
+
throw this.unimplemented('Not implemented on web.');
|
|
784
|
+
}
|
|
785
|
+
async addGeofence(_options) {
|
|
786
|
+
throw this.unimplemented('Not implemented on web.');
|
|
787
|
+
}
|
|
788
|
+
async addGeofences(_options) {
|
|
789
|
+
throw this.unimplemented('Not implemented on web.');
|
|
790
|
+
}
|
|
791
|
+
async removeGeofence(_options) {
|
|
792
|
+
throw this.unimplemented('Not implemented on web.');
|
|
793
|
+
}
|
|
794
|
+
async removeGeofences(_options) {
|
|
795
|
+
throw this.unimplemented('Not implemented on web.');
|
|
796
|
+
}
|
|
797
|
+
async getGeofences() {
|
|
798
|
+
throw this.unimplemented('Not implemented on web.');
|
|
799
|
+
}
|
|
800
|
+
async getGeofence(_options) {
|
|
801
|
+
throw this.unimplemented('Not implemented on web.');
|
|
802
|
+
}
|
|
803
|
+
async geofenceExists(_options) {
|
|
804
|
+
throw this.unimplemented('Not implemented on web.');
|
|
805
|
+
}
|
|
806
|
+
async getHeartbeatStatus() {
|
|
807
|
+
throw this.unimplemented('Not implemented on web.');
|
|
808
|
+
}
|
|
809
|
+
async getProviderState() {
|
|
810
|
+
throw this.unimplemented('Not implemented on web.');
|
|
811
|
+
}
|
|
812
|
+
async isPowerSaveMode() {
|
|
813
|
+
throw this.unimplemented('Not implemented on web.');
|
|
814
|
+
}
|
|
815
|
+
async getBatteryOptimizationStatus() {
|
|
816
|
+
throw this.unimplemented('Not implemented on web.');
|
|
817
|
+
}
|
|
818
|
+
async openBatteryOptimizationSettings() {
|
|
819
|
+
throw this.unimplemented('Not implemented on web.');
|
|
820
|
+
}
|
|
821
|
+
async getPowerManagerInfo() {
|
|
822
|
+
throw this.unimplemented('Not implemented on web.');
|
|
823
|
+
}
|
|
824
|
+
async openPowerManagerSettings() {
|
|
825
|
+
throw this.unimplemented('Not implemented on web.');
|
|
826
|
+
}
|
|
827
|
+
async openLocationSettings() {
|
|
828
|
+
throw this.unimplemented('Not implemented on web.');
|
|
829
|
+
}
|
|
830
|
+
async openAppSettings() {
|
|
831
|
+
throw this.unimplemented('Not implemented on web.');
|
|
832
|
+
}
|
|
833
|
+
/** Best-effort values parsed from `navigator.userAgent`; `platform` is `'web'`. */
|
|
834
|
+
async getDeviceInfo() {
|
|
835
|
+
const nav = typeof navigator !== 'undefined' && navigator ? navigator : undefined;
|
|
836
|
+
return webDeviceInfo(nav && typeof nav.userAgent === 'string' ? nav.userAgent : '', nav ? nav.platform : undefined);
|
|
837
|
+
}
|
|
838
|
+
async getSensors() {
|
|
839
|
+
throw this.unimplemented('Not implemented on web.');
|
|
840
|
+
}
|
|
841
|
+
/** `location` comes from the Permissions API (`'prompt'` when it cannot tell); the other three are `'granted'`. */
|
|
842
|
+
async checkPermissions() {
|
|
843
|
+
const location = await queryLocationPermission();
|
|
844
|
+
return {
|
|
845
|
+
location: location || 'prompt',
|
|
846
|
+
backgroundLocation: 'granted',
|
|
847
|
+
activityRecognition: 'granted',
|
|
848
|
+
notifications: 'granted',
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
/** When `location` is requested and still undecided, asks for a fix so the browser prompts, then reports. */
|
|
852
|
+
async requestPermissions(options) {
|
|
853
|
+
const requested = options && Array.isArray(options.permissions) ? options.permissions : ALL_PERMISSIONS;
|
|
854
|
+
const status = await this.checkPermissions();
|
|
855
|
+
const geolocation = browserGeolocation();
|
|
856
|
+
if (requested.indexOf('location') < 0 || status.location !== 'prompt' || !geolocation) {
|
|
857
|
+
return status;
|
|
858
|
+
}
|
|
859
|
+
const answer = await probeLocationPermission(geolocation);
|
|
860
|
+
// A granted probe is proof, even for a one-time grant the Permissions API still reports as 'prompt'. Otherwise
|
|
861
|
+
// prefer the Permissions API: a dismissed prompt fails the probe as denied but leaves the permission at 'prompt'.
|
|
862
|
+
status.location = answer === 'granted' ? answer : (await queryLocationPermission()) || answer;
|
|
863
|
+
return status;
|
|
864
|
+
}
|
|
865
|
+
async log(_options) {
|
|
866
|
+
throw this.unimplemented('Not implemented on web.');
|
|
867
|
+
}
|
|
868
|
+
async getLog(_options) {
|
|
869
|
+
throw this.unimplemented('Not implemented on web.');
|
|
870
|
+
}
|
|
871
|
+
async destroyLog() {
|
|
872
|
+
throw this.unimplemented('Not implemented on web.');
|
|
873
|
+
}
|
|
874
|
+
async uploadLog(_options) {
|
|
875
|
+
throw this.unimplemented('Not implemented on web.');
|
|
876
|
+
}
|
|
877
|
+
async emailLog(_options) {
|
|
878
|
+
throw this.unimplemented('Not implemented on web.');
|
|
879
|
+
}
|
|
880
|
+
requireReady() {
|
|
881
|
+
if (!this.isReady) {
|
|
882
|
+
throw webError('NOT_READY', 'Call ready() before using this method.');
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
requireGeolocation() {
|
|
886
|
+
const geolocation = browserGeolocation();
|
|
887
|
+
if (!geolocation) {
|
|
888
|
+
throw webError('UNAVAILABLE', 'navigator.geolocation is not available.');
|
|
889
|
+
}
|
|
890
|
+
return geolocation;
|
|
891
|
+
}
|
|
892
|
+
/** `persistence.extras` merged with the call's extras (the call wins), like records created on Android. */
|
|
893
|
+
recordExtras(extras) {
|
|
894
|
+
const persisted = this.config.persistence ? this.config.persistence.extras : undefined;
|
|
895
|
+
const merged = {};
|
|
896
|
+
if (isPlainObject(persisted)) {
|
|
897
|
+
Object.assign(merged, cloneJson(persisted));
|
|
898
|
+
}
|
|
899
|
+
if (isPlainObject(extras)) {
|
|
900
|
+
Object.assign(merged, cloneJson(extras));
|
|
901
|
+
}
|
|
902
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Like `notifyListeners`, but each listener gets its own copy of the payload (as on native, where every listener
|
|
906
|
+
* receives deserialized data), and a throwing listener neither skips the others nor fails the calling method.
|
|
907
|
+
*/
|
|
908
|
+
emit(eventName, data) {
|
|
909
|
+
const listeners = this.listeners[eventName];
|
|
910
|
+
if (!listeners) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
for (const listener of listeners.slice()) {
|
|
914
|
+
try {
|
|
915
|
+
listener(cloneJson(data));
|
|
916
|
+
}
|
|
917
|
+
catch (e) {
|
|
918
|
+
console.error(`LocationTracking: "${eventName}" listener threw`, e);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
925
|
+
__proto__: null,
|
|
926
|
+
LocationTrackingWeb: LocationTrackingWeb
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
exports.Events = Events;
|
|
930
|
+
exports.LocationTracking = LocationTracking;
|
|
931
|
+
exports.on = on;
|
|
932
|
+
exports.onActivityChange = onActivityChange;
|
|
933
|
+
exports.onAuthorization = onAuthorization;
|
|
934
|
+
exports.onConnectivityChange = onConnectivityChange;
|
|
935
|
+
exports.onEnabledChange = onEnabledChange;
|
|
936
|
+
exports.onGeofence = onGeofence;
|
|
937
|
+
exports.onGeofencesChange = onGeofencesChange;
|
|
938
|
+
exports.onHeartbeat = onHeartbeat;
|
|
939
|
+
exports.onHttp = onHttp;
|
|
940
|
+
exports.onLocation = onLocation;
|
|
941
|
+
exports.onMotionChange = onMotionChange;
|
|
942
|
+
exports.onNotificationAction = onNotificationAction;
|
|
943
|
+
exports.onPowerSaveChange = onPowerSaveChange;
|
|
944
|
+
exports.onProviderChange = onProviderChange;
|
|
945
|
+
//# sourceMappingURL=plugin.cjs.js.map
|