therms-device-tracker 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/ARCHITECTURE.md +145 -0
  2. package/CHANGELOG.md +26 -0
  3. package/LICENSE +21 -0
  4. package/README.md +386 -0
  5. package/android/build.gradle +25 -0
  6. package/android/src/main/AndroidManifest.xml +23 -0
  7. package/android/src/main/java/expo/modules/thermsdevicetracker/ActivityRecognitionProvider.kt +109 -0
  8. package/android/src/main/java/expo/modules/thermsdevicetracker/GeofenceProvider.kt +184 -0
  9. package/android/src/main/java/expo/modules/thermsdevicetracker/GeofenceTransitionReceiver.kt +34 -0
  10. package/android/src/main/java/expo/modules/thermsdevicetracker/LocationProvider.kt +84 -0
  11. package/android/src/main/java/expo/modules/thermsdevicetracker/LocationStore.kt +150 -0
  12. package/android/src/main/java/expo/modules/thermsdevicetracker/ScheduleProvider.kt +55 -0
  13. package/android/src/main/java/expo/modules/thermsdevicetracker/SyncProvider.kt +292 -0
  14. package/android/src/main/java/expo/modules/thermsdevicetracker/ThermsDeviceTrackerModule.kt +726 -0
  15. package/android/src/main/java/expo/modules/thermsdevicetracker/ThermsDeviceTrackerModuleSharedObject.kt +23 -0
  16. package/android/src/main/java/expo/modules/thermsdevicetracker/ThermsLocationService.kt +129 -0
  17. package/app.plugin.js +1 -0
  18. package/build/DeviceSettings.d.ts +14 -0
  19. package/build/DeviceSettings.d.ts.map +1 -0
  20. package/build/DeviceSettings.js +24 -0
  21. package/build/DeviceSettings.js.map +1 -0
  22. package/build/Logger.d.ts +13 -0
  23. package/build/Logger.d.ts.map +1 -0
  24. package/build/Logger.js +27 -0
  25. package/build/Logger.js.map +1 -0
  26. package/build/NativeModule.d.ts +51 -0
  27. package/build/NativeModule.d.ts.map +1 -0
  28. package/build/NativeModule.js +159 -0
  29. package/build/NativeModule.js.map +1 -0
  30. package/build/ThermsDeviceTracker.types.d.ts +204 -0
  31. package/build/ThermsDeviceTracker.types.d.ts.map +1 -0
  32. package/build/ThermsDeviceTracker.types.js +34 -0
  33. package/build/ThermsDeviceTracker.types.js.map +1 -0
  34. package/build/ThermsDeviceTrackerModule.d.ts +43 -0
  35. package/build/ThermsDeviceTrackerModule.d.ts.map +1 -0
  36. package/build/ThermsDeviceTrackerModule.js +3 -0
  37. package/build/ThermsDeviceTrackerModule.js.map +1 -0
  38. package/build/ThermsDeviceTrackerModule.web.d.ts +47 -0
  39. package/build/ThermsDeviceTrackerModule.web.d.ts.map +1 -0
  40. package/build/ThermsDeviceTrackerModule.web.js +132 -0
  41. package/build/ThermsDeviceTrackerModule.web.js.map +1 -0
  42. package/build/ThermsDeviceTrackerModuleSharedObject.d.ts +46 -0
  43. package/build/ThermsDeviceTrackerModuleSharedObject.d.ts.map +1 -0
  44. package/build/ThermsDeviceTrackerModuleSharedObject.js +24 -0
  45. package/build/ThermsDeviceTrackerModuleSharedObject.js.map +1 -0
  46. package/build/index.d.ts +101 -0
  47. package/build/index.d.ts.map +1 -0
  48. package/build/index.js +221 -0
  49. package/build/index.js.map +1 -0
  50. package/build/plugin/index.d.ts +14 -0
  51. package/build/plugin/index.d.ts.map +1 -0
  52. package/build/plugin/index.js +83 -0
  53. package/build/plugin/index.js.map +1 -0
  54. package/build/tsconfig.tsbuildinfo +1 -0
  55. package/expo-module.config.json +9 -0
  56. package/ios/GeofenceManager.swift +221 -0
  57. package/ios/LocationProvider.swift +32 -0
  58. package/ios/LocationStore.swift +98 -0
  59. package/ios/MotionActivityProvider.swift +109 -0
  60. package/ios/ProviderMonitor.swift +33 -0
  61. package/ios/ScheduleManager.swift +33 -0
  62. package/ios/SyncManager.swift +186 -0
  63. package/ios/ThermsDeviceTracker.podspec +24 -0
  64. package/ios/ThermsDeviceTrackerModule.swift +632 -0
  65. package/ios/ThermsDeviceTrackerModuleSharedObject.swift +17 -0
  66. package/ios/ThermsGeofenceTests.swift +474 -0
  67. package/package.json +95 -0
@@ -0,0 +1,23 @@
1
+ package expo.modules.thermsdevicetracker
2
+
3
+ import expo.modules.kotlin.AppContext
4
+ import expo.modules.kotlin.sharedobjects.SharedObject
5
+
6
+ /**
7
+ * Legacy + compatibility placeholder only.
8
+ * Real ThermsTracker live state handle + Class registration lives inside ThermsDeviceTrackerModule.kt.
9
+ * This class is not registered/used by current implementation.
10
+ * Consumers should import via JS: useThermsTracker from 'therms-device-tracker'.
11
+ */
12
+ class ThermsDeviceTrackerModuleSharedObject(appContext: AppContext) : SharedObject(appContext) {
13
+ // Unused legacy field retained only for historical/compat reasons.
14
+ @Deprecated("Legacy placeholder; not used. ThermsTracker proxies module state directly.")
15
+ var count: Int = 0
16
+
17
+ override fun sharedObjectDidRelease() {
18
+ super.sharedObjectDidRelease()
19
+ }
20
+ }
21
+
22
+ // typealias removed to avoid package-level redeclaration conflict with ThermsTracker class defined in ThermsDeviceTrackerModule.kt
23
+ // (legacy compatibility placeholder only)
@@ -0,0 +1,129 @@
1
+ package expo.modules.thermsdevicetracker
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.app.Service
8
+ import android.content.Context
9
+ import android.content.Intent
10
+ import android.os.Build
11
+ import android.os.IBinder
12
+ import android.os.Looper
13
+ import androidx.core.app.NotificationCompat
14
+ import com.google.android.gms.location.*
15
+
16
+ /**
17
+ * Foreground service used for background location + activity tracking.
18
+ * Started by ThermsDeviceTrackerModule when enableBackground = true.
19
+ */
20
+ class ThermsLocationService : Service() {
21
+
22
+ private lateinit var fusedClient: FusedLocationProviderClient
23
+ private var locationCallback: LocationCallback? = null
24
+
25
+ companion object {
26
+ const val CHANNEL_ID = "therms_tracking_channel"
27
+ const val NOTIFICATION_ID = 4242
28
+ const val ACTION_START = "expo.modules.thermsdevicetracker.START_TRACKING"
29
+ const val ACTION_STOP = "expo.modules.thermsdevicetracker.STOP_TRACKING"
30
+
31
+ // For notifying the JS module (broadcast based)
32
+ const val BROADCAST_ACTION = "expo.modules.thermsdevicetracker.LOCATION_UPDATE"
33
+ const val EXTRA_LAT = "lat"
34
+ const val EXTRA_LON = "lon"
35
+ const val EXTRA_ACCURACY = "accuracy"
36
+ const val EXTRA_SPEED = "speed"
37
+ const val EXTRA_TIMESTAMP = "ts"
38
+ }
39
+
40
+ override fun onCreate() {
41
+ super.onCreate()
42
+ fusedClient = LocationServices.getFusedLocationProviderClient(this)
43
+ }
44
+
45
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
46
+ when (intent?.action) {
47
+ ACTION_STOP -> {
48
+ stopTracking()
49
+ stopSelf()
50
+ return START_NOT_STICKY
51
+ }
52
+ else -> startTracking()
53
+ }
54
+ return START_STICKY
55
+ }
56
+
57
+ private fun startTracking() {
58
+ startForeground(NOTIFICATION_ID, createNotification())
59
+
60
+ val request = LocationRequest.Builder(Priority.PRIORITY_BALANCED_POWER_ACCURACY, 10000L)
61
+ .setMinUpdateDistanceMeters(5f)
62
+ .build()
63
+
64
+ locationCallback = object : LocationCallback() {
65
+ override fun onLocationResult(result: LocationResult) {
66
+ result.lastLocation?.let { loc ->
67
+ val i = Intent(BROADCAST_ACTION).apply {
68
+ putExtra(EXTRA_LAT, loc.latitude)
69
+ putExtra(EXTRA_LON, loc.longitude)
70
+ putExtra(EXTRA_ACCURACY, if (loc.hasAccuracy()) loc.accuracy else -1f)
71
+ putExtra(EXTRA_SPEED, if (loc.hasSpeed()) loc.speed else -1f)
72
+ putExtra(EXTRA_TIMESTAMP, loc.time)
73
+ }
74
+ sendBroadcast(i)
75
+ }
76
+ }
77
+ }
78
+
79
+ try {
80
+ fusedClient.requestLocationUpdates(request, locationCallback!!, Looper.getMainLooper())
81
+ } catch (e: SecurityException) {
82
+ // permission lost
83
+ }
84
+ }
85
+
86
+ private fun stopTracking() {
87
+ locationCallback?.let { fusedClient.removeLocationUpdates(it) }
88
+ locationCallback = null
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
90
+ stopForeground(STOP_FOREGROUND_REMOVE)
91
+ } else {
92
+ @Suppress("DEPRECATION")
93
+ stopForeground(true)
94
+ }
95
+ }
96
+
97
+ private fun createNotification(): Notification {
98
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
99
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
100
+ val channel = NotificationChannel(
101
+ CHANNEL_ID,
102
+ "THERMS Tracking",
103
+ NotificationManager.IMPORTANCE_LOW
104
+ )
105
+ manager.createNotificationChannel(channel)
106
+ }
107
+
108
+ val notificationIntent = packageManager.getLaunchIntentForPackage(packageName)
109
+ val pending = PendingIntent.getActivity(
110
+ this, 0, notificationIntent,
111
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
112
+ )
113
+
114
+ return NotificationCompat.Builder(this, CHANNEL_ID)
115
+ .setContentTitle("THERMS Tracking")
116
+ .setContentText("Tracking your location and activity")
117
+ .setSmallIcon(android.R.drawable.ic_menu_mylocation)
118
+ .setContentIntent(pending)
119
+ .setOngoing(true)
120
+ .build()
121
+ }
122
+
123
+ override fun onBind(intent: Intent?): IBinder? = null
124
+
125
+ override fun onDestroy() {
126
+ stopTracking()
127
+ super.onDestroy()
128
+ }
129
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./build/plugin').default;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * DeviceSettings - handles OS-level device settings (battery optimization, etc.)
3
+ * Directly inspired by the reference library's DeviceSettings module.
4
+ *
5
+ * This provides a place to surface power-saving warnings, request ignoring battery optimizations, etc.
6
+ */
7
+ export declare class DeviceSettings {
8
+ isIgnoringBatteryOptimizations(): Promise<boolean>;
9
+ requestIgnoreBatteryOptimizations(): Promise<void>;
10
+ getDeviceInfo(): Promise<any>;
11
+ }
12
+ export declare const deviceSettings: DeviceSettings;
13
+ export default deviceSettings;
14
+ //# sourceMappingURL=DeviceSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceSettings.d.ts","sourceRoot":"","sources":["../src/DeviceSettings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qBAAa,cAAc;IACnB,8BAA8B,IAAI,OAAO,CAAC,OAAO,CAAC;IAMlD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAOlD,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC;CAIpC;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC;AAEnD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * DeviceSettings - handles OS-level device settings (battery optimization, etc.)
3
+ * Directly inspired by the reference library's DeviceSettings module.
4
+ *
5
+ * This provides a place to surface power-saving warnings, request ignoring battery optimizations, etc.
6
+ */
7
+ export class DeviceSettings {
8
+ async isIgnoringBatteryOptimizations() {
9
+ // On Android this would call native. On iOS usually not relevant.
10
+ // For now, stub.
11
+ return true;
12
+ }
13
+ async requestIgnoreBatteryOptimizations() {
14
+ console.warn('[ThermsDeviceTracker] requestIgnoreBatteryOptimizations is a no-op on this platform or needs native implementation.');
15
+ // TODO: implement via native module when adding full DeviceSettings support
16
+ }
17
+ async getDeviceInfo() {
18
+ // Could delegate to native
19
+ return { platform: 'unknown' };
20
+ }
21
+ }
22
+ export const deviceSettings = new DeviceSettings();
23
+ export default deviceSettings;
24
+ //# sourceMappingURL=DeviceSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceSettings.js","sourceRoot":"","sources":["../src/DeviceSettings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,OAAO,cAAc;IACzB,KAAK,CAAC,8BAA8B;QAClC,kEAAkE;QAClE,iBAAiB;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,iCAAiC;QACrC,OAAO,CAAC,IAAI,CACV,qHAAqH,CACtH,CAAC;QACF,4EAA4E;IAC9E,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,2BAA2B;QAC3B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;AAEnD,eAAe,cAAc,CAAC","sourcesContent":["/**\n * DeviceSettings - handles OS-level device settings (battery optimization, etc.)\n * Directly inspired by the reference library's DeviceSettings module.\n *\n * This provides a place to surface power-saving warnings, request ignoring battery optimizations, etc.\n */\n\nexport class DeviceSettings {\n async isIgnoringBatteryOptimizations(): Promise<boolean> {\n // On Android this would call native. On iOS usually not relevant.\n // For now, stub.\n return true;\n }\n\n async requestIgnoreBatteryOptimizations(): Promise<void> {\n console.warn(\n '[ThermsDeviceTracker] requestIgnoreBatteryOptimizations is a no-op on this platform or needs native implementation.'\n );\n // TODO: implement via native module when adding full DeviceSettings support\n }\n\n async getDeviceInfo(): Promise<any> {\n // Could delegate to native\n return { platform: 'unknown' };\n }\n}\n\nexport const deviceSettings = new DeviceSettings();\n\nexport default deviceSettings;\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Logger - dedicated cross-cutting concern.
3
+ * Inspired by the rich logger in react-native-background-geolocation.
4
+ */
5
+ export declare const Logger: {
6
+ error(msg: string, ...args: any[]): void;
7
+ warn(msg: string, ...args: any[]): void;
8
+ info(msg: string, ...args: any[]): void;
9
+ debug(msg: string, ...args: any[]): void;
10
+ verbose(msg: string, ...args: any[]): void;
11
+ };
12
+ export default Logger;
13
+ //# sourceMappingURL=Logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,MAAM;eACN,MAAM,WAAW,GAAG,EAAE;cAIvB,MAAM,WAAW,GAAG,EAAE;cAGtB,MAAM,WAAW,GAAG,EAAE;eAGrB,MAAM,WAAW,GAAG,EAAE;iBAGpB,MAAM,WAAW,GAAG,EAAE;CAGpC,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Logger - dedicated cross-cutting concern.
3
+ * Inspired by the rich logger in react-native-background-geolocation.
4
+ */
5
+ const TAG = 'ThermsDeviceTracker';
6
+ export const Logger = {
7
+ error(msg, ...args) {
8
+ console.error(`[${TAG}] ERROR:`, msg, ...args);
9
+ // Could delegate to native log later
10
+ },
11
+ warn(msg, ...args) {
12
+ console.warn(`[${TAG}] WARN:`, msg, ...args);
13
+ },
14
+ info(msg, ...args) {
15
+ console.log(`[${TAG}] INFO:`, msg, ...args);
16
+ },
17
+ debug(msg, ...args) {
18
+ if (__DEV__)
19
+ console.log(`[${TAG}] DEBUG:`, msg, ...args);
20
+ },
21
+ verbose(msg, ...args) {
22
+ if (__DEV__)
23
+ console.log(`[${TAG}] VERBOSE:`, msg, ...args);
24
+ },
25
+ };
26
+ export default Logger;
27
+ //# sourceMappingURL=Logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Logger.js","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,qBAAqB,CAAC;AAElC,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,CAAC,GAAW,EAAE,GAAG,IAAW;QAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC/C,qCAAqC;IACvC,CAAC;IACD,IAAI,CAAC,GAAW,EAAE,GAAG,IAAW;QAC9B,OAAO,CAAC,IAAI,CAAC,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,CAAC,GAAW,EAAE,GAAG,IAAW;QAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,CAAC,GAAW,EAAE,GAAG,IAAW;QAC/B,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,CAAC,GAAW,EAAE,GAAG,IAAW;QACjC,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,YAAY,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9D,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC","sourcesContent":["/**\n * Logger - dedicated cross-cutting concern.\n * Inspired by the rich logger in react-native-background-geolocation.\n */\n\nconst TAG = 'ThermsDeviceTracker';\n\nexport const Logger = {\n error(msg: string, ...args: any[]) {\n console.error(`[${TAG}] ERROR:`, msg, ...args);\n // Could delegate to native log later\n },\n warn(msg: string, ...args: any[]) {\n console.warn(`[${TAG}] WARN:`, msg, ...args);\n },\n info(msg: string, ...args: any[]) {\n console.log(`[${TAG}] INFO:`, msg, ...args);\n },\n debug(msg: string, ...args: any[]) {\n if (__DEV__) console.log(`[${TAG}] DEBUG:`, msg, ...args);\n },\n verbose(msg: string, ...args: any[]) {\n if (__DEV__) console.log(`[${TAG}] VERBOSE:`, msg, ...args);\n },\n};\n\nexport default Logger;\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * NativeModule abstraction layer.
3
+ *
4
+ * Inspired by the excellent separation in react-native-background-geolocation's NativeModule.js.
5
+ *
6
+ * Responsibilities:
7
+ * - Wrap the raw Expo native module
8
+ * - Handle event subscriptions with validation and cleanup
9
+ * - Config normalization / validation
10
+ * - Bridge concerns isolated from the public facade
11
+ */
12
+ import { ThermsConfig, Geofence } from './ThermsDeviceTracker.types';
13
+ export declare class NativeModuleWrapper {
14
+ private subscriptions;
15
+ private validateEvent;
16
+ addListener(event: string, listener: (data: any) => void): any;
17
+ removeAllListeners(): void;
18
+ ready(config?: ThermsConfig): Promise<any>;
19
+ start(config?: ThermsConfig): Promise<any>;
20
+ stop(): Promise<any>;
21
+ getState(): any;
22
+ getCurrentLocationAsync(): any;
23
+ getProviderState(): any;
24
+ addGeofence(geofence: Geofence): any;
25
+ addGeofences(geofences: Geofence[]): any;
26
+ removeGeofence(identifier: string): any;
27
+ removeGeofences(identifiers?: string[]): any;
28
+ getGeofences(): Promise<Geofence[]>;
29
+ startGeofences(): any;
30
+ getLocations(): any;
31
+ getCount(): any;
32
+ destroyLocations(): any;
33
+ destroyLocation(uuid?: string): any;
34
+ insertLocation(location: any): any;
35
+ startSchedule(): any;
36
+ stopSchedule(): any;
37
+ startSync(): any;
38
+ stopSync(): any;
39
+ }
40
+ /**
41
+ * Lightweight normalizer for compound config (especially the opt-in `sync` section).
42
+ * Provides early, clear validation and sensible defaults before crossing the bridge.
43
+ * This improves DX and reduces silent fallbacks in native code.
44
+ *
45
+ * Note on sync.interval: platform-enforced mins apply (Android WorkManager >=15min for periodic).
46
+ */
47
+ export declare function normalizeConfig(config?: any): any;
48
+ export declare const NativeModuleInstance: NativeModuleWrapper;
49
+ export { NativeModuleInstance as NativeModule };
50
+ export default NativeModuleInstance;
51
+ //# sourceMappingURL=NativeModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeModule.d.ts","sourceRoot":"","sources":["../src/NativeModule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,YAAY,EAAS,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAoB5E,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,aAAa,CAAa;IAElC,OAAO,CAAC,aAAa;IAMrB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI;IAUxD,kBAAkB;IAaZ,KAAK,CAAC,MAAM,CAAC,EAAE,YAAY;IAS3B,KAAK,CAAC,MAAM,CAAC,EAAE,YAAY;IAS3B,IAAI;IAKV,QAAQ;IAIR,uBAAuB;IAIvB,gBAAgB;IAMhB,WAAW,CAAC,QAAQ,EAAE,QAAQ;IAI9B,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE;IAIlC,cAAc,CAAC,UAAU,EAAE,MAAM;IAIjC,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE;IAItC,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAInC,cAAc;IAKd,YAAY;IAKZ,QAAQ;IAIR,gBAAgB;IAIhB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM;IAI7B,cAAc,CAAC,QAAQ,EAAE,GAAG;IAK5B,aAAa;IAIb,YAAY;IAMZ,SAAS;IAIT,QAAQ;CAGT;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,OAgB3C;AAED,eAAO,MAAM,oBAAoB,qBAA4B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,IAAI,YAAY,EAAE,CAAC;AAChD,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * NativeModule abstraction layer.
3
+ *
4
+ * Inspired by the excellent separation in react-native-background-geolocation's NativeModule.js.
5
+ *
6
+ * Responsibilities:
7
+ * - Wrap the raw Expo native module
8
+ * - Handle event subscriptions with validation and cleanup
9
+ * - Config normalization / validation
10
+ * - Bridge concerns isolated from the public facade
11
+ */
12
+ import { requireNativeModule } from 'expo';
13
+ import { Event } from './ThermsDeviceTracker.types';
14
+ const VALID_EVENT_NAMES = new Set(Object.values(Event));
15
+ let _rawModule = null;
16
+ function getRawModule() {
17
+ if (_rawModule)
18
+ return _rawModule;
19
+ _rawModule = requireNativeModule('ThermsDeviceTracker');
20
+ return _rawModule;
21
+ }
22
+ export class NativeModuleWrapper {
23
+ subscriptions = [];
24
+ validateEvent(event) {
25
+ if (!VALID_EVENT_NAMES.has(event)) {
26
+ throw new Error(`[ThermsDeviceTracker] Unknown event '${event}'`);
27
+ }
28
+ }
29
+ addListener(event, listener) {
30
+ this.validateEvent(event);
31
+ const module = getRawModule();
32
+ const sub = module.addListener?.(event, listener);
33
+ if (sub) {
34
+ this.subscriptions.push(sub);
35
+ }
36
+ return sub;
37
+ }
38
+ removeAllListeners() {
39
+ this.subscriptions.forEach((sub) => {
40
+ try {
41
+ sub.remove?.();
42
+ }
43
+ catch { }
44
+ });
45
+ this.subscriptions = [];
46
+ try {
47
+ getRawModule().removeAllListeners?.();
48
+ }
49
+ catch { }
50
+ }
51
+ // Config + lifecycle (reference style)
52
+ async ready(config) {
53
+ const normalized = normalizeConfig(config);
54
+ const module = getRawModule();
55
+ if (typeof module.ready === 'function') {
56
+ return module.ready(normalized);
57
+ }
58
+ return this.start(normalized);
59
+ }
60
+ async start(config) {
61
+ const normalized = normalizeConfig(config);
62
+ const module = getRawModule();
63
+ if (normalized && typeof module.setConfig === 'function') {
64
+ await module.setConfig(normalized);
65
+ }
66
+ return module.startTrackingAsync?.(normalized) || module.start?.();
67
+ }
68
+ async stop() {
69
+ const module = getRawModule();
70
+ return module.stopTrackingAsync?.() || module.stop?.();
71
+ }
72
+ getState() {
73
+ return getRawModule().getTrackingStatus?.() || getRawModule().getState?.();
74
+ }
75
+ getCurrentLocationAsync() {
76
+ return getRawModule().getCurrentLocationAsync?.();
77
+ }
78
+ getProviderState() {
79
+ const mod = getRawModule();
80
+ return mod.getProviderState?.() || mod.getTrackingStatus?.();
81
+ }
82
+ // Geofencing delegation
83
+ addGeofence(geofence) {
84
+ return getRawModule().addGeofence?.(geofence);
85
+ }
86
+ addGeofences(geofences) {
87
+ return getRawModule().addGeofences?.(geofences);
88
+ }
89
+ removeGeofence(identifier) {
90
+ return getRawModule().removeGeofence?.(identifier);
91
+ }
92
+ removeGeofences(identifiers) {
93
+ return getRawModule().removeGeofences?.(identifiers);
94
+ }
95
+ getGeofences() {
96
+ return getRawModule().getGeofences?.();
97
+ }
98
+ startGeofences() {
99
+ return getRawModule().startGeofences?.();
100
+ }
101
+ // Persistence
102
+ getLocations() {
103
+ const m = getRawModule();
104
+ return m.getLocations?.() || m.getCurrentSessionHistory?.();
105
+ }
106
+ getCount() {
107
+ return getRawModule().getCount?.();
108
+ }
109
+ destroyLocations() {
110
+ return getRawModule().destroyLocations?.();
111
+ }
112
+ destroyLocation(uuid) {
113
+ return getRawModule().destroyLocation?.(uuid);
114
+ }
115
+ insertLocation(location) {
116
+ return getRawModule().insertLocation?.(location);
117
+ }
118
+ // Scheduler
119
+ startSchedule() {
120
+ return getRawModule().startSchedule?.();
121
+ }
122
+ stopSchedule() {
123
+ return getRawModule().stopSchedule?.();
124
+ }
125
+ // Background sync (delegation to native impl: SyncManager on iOS, SyncProvider on Android).
126
+ // See ThermsConfig.sync + onSync for details (opt-in, config-driven or explicit start/stopSync).
127
+ startSync() {
128
+ return getRawModule().startSync?.();
129
+ }
130
+ stopSync() {
131
+ return getRawModule().stopSync?.();
132
+ }
133
+ }
134
+ /**
135
+ * Lightweight normalizer for compound config (especially the opt-in `sync` section).
136
+ * Provides early, clear validation and sensible defaults before crossing the bridge.
137
+ * This improves DX and reduces silent fallbacks in native code.
138
+ *
139
+ * Note on sync.interval: platform-enforced mins apply (Android WorkManager >=15min for periodic).
140
+ */
141
+ export function normalizeConfig(config) {
142
+ if (!config)
143
+ return config;
144
+ const out = { ...config };
145
+ if (out.sync) {
146
+ const s = { ...out.sync };
147
+ // url is required for enabled sync (native gracefully handles missing, but we surface it early)
148
+ if (s.enabled && !s.url) {
149
+ // Non-fatal in case someone disables later; native will emit disabled.
150
+ }
151
+ s.batch = s.batch ?? true; // match native default
152
+ out.sync = s;
153
+ }
154
+ return out;
155
+ }
156
+ export const NativeModuleInstance = new NativeModuleWrapper();
157
+ export { NativeModuleInstance as NativeModule };
158
+ export default NativeModuleInstance;
159
+ //# sourceMappingURL=NativeModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeModule.js","sourceRoot":"","sources":["../src/NativeModule.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativeModule abstraction layer.\n *\n * Inspired by the excellent separation in react-native-background-geolocation's NativeModule.js.\n *\n * Responsibilities:\n * - Wrap the raw Expo native module\n * - Handle event subscriptions with validation and cleanup\n * - Config normalization / validation\n * - Bridge concerns isolated from the public facade\n */\n\nimport { requireNativeModule } from 'expo';\n\nimport { ThermsConfig, Event, Geofence } from './ThermsDeviceTracker.types';\n\nconst VALID_EVENT_NAMES = new Set(Object.values(Event));\n\ntype RawModule = any & {\n ready?: (config?: ThermsConfig) => Promise<any>;\n start?: () => Promise<void>;\n stop?: () => Promise<void>;\n getState?: () => any;\n // ... other raw methods exposed by the Swift/Kotlin module\n};\n\nlet _rawModule: RawModule | null = null;\n\nfunction getRawModule(): RawModule {\n if (_rawModule) return _rawModule;\n _rawModule = requireNativeModule<RawModule>('ThermsDeviceTracker');\n return _rawModule;\n}\n\nexport class NativeModuleWrapper {\n private subscriptions: any[] = [];\n\n private validateEvent(event: string) {\n if (!VALID_EVENT_NAMES.has(event as any)) {\n throw new Error(`[ThermsDeviceTracker] Unknown event '${event}'`);\n }\n }\n\n addListener(event: string, listener: (data: any) => void) {\n this.validateEvent(event);\n const module = getRawModule();\n const sub = (module as any).addListener?.(event, listener);\n if (sub) {\n this.subscriptions.push(sub);\n }\n return sub;\n }\n\n removeAllListeners() {\n this.subscriptions.forEach((sub) => {\n try {\n sub.remove?.();\n } catch {}\n });\n this.subscriptions = [];\n try {\n (getRawModule() as any).removeAllListeners?.();\n } catch {}\n }\n\n // Config + lifecycle (reference style)\n async ready(config?: ThermsConfig) {\n const normalized = normalizeConfig(config);\n const module = getRawModule();\n if (typeof (module as any).ready === 'function') {\n return (module as any).ready(normalized);\n }\n return this.start(normalized);\n }\n\n async start(config?: ThermsConfig) {\n const normalized = normalizeConfig(config);\n const module = getRawModule();\n if (normalized && typeof (module as any).setConfig === 'function') {\n await (module as any).setConfig(normalized);\n }\n return (module as any).startTrackingAsync?.(normalized) || (module as any).start?.();\n }\n\n async stop() {\n const module = getRawModule();\n return (module as any).stopTrackingAsync?.() || (module as any).stop?.();\n }\n\n getState() {\n return (getRawModule() as any).getTrackingStatus?.() || (getRawModule() as any).getState?.();\n }\n\n getCurrentLocationAsync() {\n return (getRawModule() as any).getCurrentLocationAsync?.();\n }\n\n getProviderState() {\n const mod = getRawModule();\n return (mod as any).getProviderState?.() || (mod as any).getTrackingStatus?.();\n }\n\n // Geofencing delegation\n addGeofence(geofence: Geofence) {\n return (getRawModule() as any).addGeofence?.(geofence);\n }\n\n addGeofences(geofences: Geofence[]) {\n return (getRawModule() as any).addGeofences?.(geofences);\n }\n\n removeGeofence(identifier: string) {\n return (getRawModule() as any).removeGeofence?.(identifier);\n }\n\n removeGeofences(identifiers?: string[]) {\n return (getRawModule() as any).removeGeofences?.(identifiers);\n }\n\n getGeofences(): Promise<Geofence[]> {\n return (getRawModule() as any).getGeofences?.();\n }\n\n startGeofences() {\n return (getRawModule() as any).startGeofences?.();\n }\n\n // Persistence\n getLocations() {\n const m = getRawModule() as any;\n return m.getLocations?.() || m.getCurrentSessionHistory?.();\n }\n\n getCount() {\n return (getRawModule() as any).getCount?.();\n }\n\n destroyLocations() {\n return (getRawModule() as any).destroyLocations?.();\n }\n\n destroyLocation(uuid?: string) {\n return (getRawModule() as any).destroyLocation?.(uuid);\n }\n\n insertLocation(location: any) {\n return (getRawModule() as any).insertLocation?.(location);\n }\n\n // Scheduler\n startSchedule() {\n return (getRawModule() as any).startSchedule?.();\n }\n\n stopSchedule() {\n return (getRawModule() as any).stopSchedule?.();\n }\n\n // Background sync (delegation to native impl: SyncManager on iOS, SyncProvider on Android).\n // See ThermsConfig.sync + onSync for details (opt-in, config-driven or explicit start/stopSync).\n startSync() {\n return (getRawModule() as any).startSync?.();\n }\n\n stopSync() {\n return (getRawModule() as any).stopSync?.();\n }\n}\n\n/**\n * Lightweight normalizer for compound config (especially the opt-in `sync` section).\n * Provides early, clear validation and sensible defaults before crossing the bridge.\n * This improves DX and reduces silent fallbacks in native code.\n *\n * Note on sync.interval: platform-enforced mins apply (Android WorkManager >=15min for periodic).\n */\nexport function normalizeConfig(config?: any) {\n if (!config) return config;\n\n const out = { ...config };\n\n if (out.sync) {\n const s = { ...out.sync };\n // url is required for enabled sync (native gracefully handles missing, but we surface it early)\n if (s.enabled && !s.url) {\n // Non-fatal in case someone disables later; native will emit disabled.\n }\n s.batch = s.batch ?? true; // match native default\n out.sync = s;\n }\n\n return out;\n}\n\nexport const NativeModuleInstance = new NativeModuleWrapper();\nexport { NativeModuleInstance as NativeModule };\nexport default NativeModuleInstance;\n"]}