@kafitra/react-native-live-tracking 0.1.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 +396 -0
- package/android/build.gradle +71 -0
- package/android/gradle.properties +7 -0
- package/android/src/main/AndroidManifest.xml +40 -0
- package/android/src/main/java/com/livetracking/LiveTrackingModuleImpl.kt +728 -0
- package/android/src/main/java/com/livetracking/LiveTrackingPackage.kt +16 -0
- package/android/src/main/java/com/livetracking/location/LocationEngine.kt +93 -0
- package/android/src/main/java/com/livetracking/network/NetworkListener.kt +127 -0
- package/android/src/main/java/com/livetracking/optimizer/ActivityRecognitionHandler.kt +248 -0
- package/android/src/main/java/com/livetracking/optimizer/MotionSleepManager.kt +130 -0
- package/android/src/main/java/com/livetracking/permissions/PermissionHandler.kt +145 -0
- package/android/src/main/java/com/livetracking/queue/QueueEngine.kt +167 -0
- package/android/src/main/java/com/livetracking/queue/QueuedLocation.kt +16 -0
- package/android/src/main/java/com/livetracking/queue/TrackingDatabase.kt +239 -0
- package/android/src/main/java/com/livetracking/receiver/BootReceiver.kt +53 -0
- package/android/src/main/java/com/livetracking/service/TrackingForegroundService.kt +145 -0
- package/android/src/main/java/com/livetracking/sync/FirebaseSyncEngine.kt +277 -0
- package/android/src/main/java/com/livetracking/sync/LocationDataPoint.kt +31 -0
- package/android/src/main/java/com/livetracking/sync/SyncEngineController.kt +220 -0
- package/android/src/main/java/com/livetracking/sync/SyncTargetConfig.kt +20 -0
- package/android/src/main/java/com/livetracking/sync/TargetHandler.kt +601 -0
- package/android/src/newarch/java/com/livetracking/LiveTrackingModule.kt +64 -0
- package/android/src/oldarch/java/com/livetracking/LiveTrackingModule.kt +70 -0
- package/android/src/test/java/com/livetracking/BackoffCalculationTest.kt +216 -0
- package/android/src/test/java/com/livetracking/BatchAccumulatorTest.kt +391 -0
- package/android/src/test/java/com/livetracking/BootReceiverTest.kt +247 -0
- package/android/src/test/java/com/livetracking/FirebaseSyncEngineTest.kt +337 -0
- package/android/src/test/java/com/livetracking/LocationEngineTest.kt +202 -0
- package/android/src/test/java/com/livetracking/MotionSleepManagerTest.kt +420 -0
- package/android/src/test/java/com/livetracking/OfflineQueueTest.kt +462 -0
- package/android/src/test/java/com/livetracking/PermissionHandlerTest.kt +200 -0
- package/android/src/test/java/com/livetracking/QueueEngineTest.kt +335 -0
- package/android/src/test/java/com/livetracking/SyncEngineControllerTest.kt +855 -0
- package/ios/ActivityRecognitionHandler.swift +196 -0
- package/ios/BackgroundModeHelper.swift +132 -0
- package/ios/FirebaseSyncEngine.swift +276 -0
- package/ios/LiveTracking-Bridging-Header.h +2 -0
- package/ios/LiveTracking.m +37 -0
- package/ios/LiveTracking.swift +773 -0
- package/ios/LocationDataPoint.swift +56 -0
- package/ios/LocationEngine.swift +160 -0
- package/ios/MotionSleepManager.swift +151 -0
- package/ios/NetworkListener.swift +105 -0
- package/ios/OfflineQueueManager.swift +503 -0
- package/ios/PermissionHandler.swift +148 -0
- package/ios/QueueEngine.swift +249 -0
- package/ios/SyncEngineController.swift +396 -0
- package/ios/SyncTargetConfig.swift +36 -0
- package/ios/TargetHandler.swift +715 -0
- package/ios/Tests/ActivityRecognitionHandlerTests.swift +259 -0
- package/ios/Tests/FirebaseSyncEngineTests.swift +303 -0
- package/ios/Tests/LocationEngineTests.swift +244 -0
- package/ios/Tests/MotionSleepManagerTests.swift +355 -0
- package/ios/Tests/NetworkListenerTests.swift +188 -0
- package/ios/Tests/OfflineQueueFlushTests.swift +375 -0
- package/ios/Tests/PermissionHandlerTests.swift +238 -0
- package/ios/Tests/QueueEngineTests.swift +346 -0
- package/ios/TrackingCleanup.swift +93 -0
- package/ios/TrackingNotificationManager.swift +187 -0
- package/lib/commonjs/EventEmitter.js +113 -0
- package/lib/commonjs/EventEmitter.js.map +1 -0
- package/lib/commonjs/LiveTracking.js +134 -0
- package/lib/commonjs/LiveTracking.js.map +1 -0
- package/lib/commonjs/NativeLiveTracking.js +21 -0
- package/lib/commonjs/NativeLiveTracking.js.map +1 -0
- package/lib/commonjs/filters/distanceTimeFilter.js +63 -0
- package/lib/commonjs/filters/distanceTimeFilter.js.map +1 -0
- package/lib/commonjs/index.js +103 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/serialization/locationSerializer.js +51 -0
- package/lib/commonjs/serialization/locationSerializer.js.map +1 -0
- package/lib/commonjs/types.js +77 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/distance.js +63 -0
- package/lib/commonjs/utils/distance.js.map +1 -0
- package/lib/commonjs/utils/retry.js +80 -0
- package/lib/commonjs/utils/retry.js.map +1 -0
- package/lib/commonjs/validation.js +463 -0
- package/lib/commonjs/validation.js.map +1 -0
- package/lib/module/EventEmitter.js +105 -0
- package/lib/module/EventEmitter.js.map +1 -0
- package/lib/module/LiveTracking.js +127 -0
- package/lib/module/LiveTracking.js.map +1 -0
- package/lib/module/NativeLiveTracking.js +16 -0
- package/lib/module/NativeLiveTracking.js.map +1 -0
- package/lib/module/filters/distanceTimeFilter.js +58 -0
- package/lib/module/filters/distanceTimeFilter.js.map +1 -0
- package/lib/module/index.js +32 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/serialization/locationSerializer.js +45 -0
- package/lib/module/serialization/locationSerializer.js.map +1 -0
- package/lib/module/types.js +94 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/distance.js +56 -0
- package/lib/module/utils/distance.js.map +1 -0
- package/lib/module/utils/retry.js +72 -0
- package/lib/module/utils/retry.js.map +1 -0
- package/lib/module/validation.js +456 -0
- package/lib/module/validation.js.map +1 -0
- package/lib/typescript/EventEmitter.d.ts +65 -0
- package/lib/typescript/EventEmitter.d.ts.map +1 -0
- package/lib/typescript/LiveTracking.d.ts +23 -0
- package/lib/typescript/LiveTracking.d.ts.map +1 -0
- package/lib/typescript/NativeLiveTracking.d.ts +25 -0
- package/lib/typescript/NativeLiveTracking.d.ts.map +1 -0
- package/lib/typescript/filters/distanceTimeFilter.d.ts +44 -0
- package/lib/typescript/filters/distanceTimeFilter.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +21 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/serialization/locationSerializer.d.ts +39 -0
- package/lib/typescript/serialization/locationSerializer.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +217 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils/distance.d.ts +38 -0
- package/lib/typescript/utils/distance.d.ts.map +1 -0
- package/lib/typescript/utils/retry.d.ts +60 -0
- package/lib/typescript/utils/retry.d.ts.map +1 -0
- package/lib/typescript/validation.d.ts +26 -0
- package/lib/typescript/validation.d.ts.map +1 -0
- package/package.json +126 -0
- package/react-native-live-tracking.podspec +47 -0
- package/src/EventEmitter.ts +118 -0
- package/src/LiveTracking.ts +159 -0
- package/src/NativeLiveTracking.ts +29 -0
- package/src/filters/distanceTimeFilter.ts +75 -0
- package/src/index.ts +51 -0
- package/src/serialization/locationSerializer.ts +57 -0
- package/src/types.ts +252 -0
- package/src/utils/distance.ts +68 -0
- package/src/utils/retry.ts +75 -0
- package/src/validation.ts +552 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript type definitions for react-native-live-tracking.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─── Enums ───────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Internal state machine for tracking lifecycle.
|
|
11
|
+
*/
|
|
12
|
+
export enum TrackingState {
|
|
13
|
+
IDLE = 'idle',
|
|
14
|
+
CONFIGURED = 'configured',
|
|
15
|
+
TRACKING = 'tracking',
|
|
16
|
+
MOTION_SLEEP = 'motion_sleep',
|
|
17
|
+
PAUSED_GPS = 'paused_gps',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Configuration Interfaces ────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Strategy used by the distance/time filter.
|
|
24
|
+
* - 'interval': accept when enough time has elapsed
|
|
25
|
+
* - 'distance': accept when enough distance has been covered
|
|
26
|
+
* - 'both': accept only when both conditions are met (default)
|
|
27
|
+
*/
|
|
28
|
+
export type OptimizationMode = 'interval' | 'distance' | 'both';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Optimization parameters for location tracking.
|
|
32
|
+
*/
|
|
33
|
+
export interface OptimizationConfig {
|
|
34
|
+
/** Minimum time interval between location updates in milliseconds. Default: 10000 */
|
|
35
|
+
intervalMs?: number;
|
|
36
|
+
/** Minimum distance change in meters to trigger an update. Default: 10 */
|
|
37
|
+
distanceFilterMeters?: number;
|
|
38
|
+
/** Whether to reduce GPS accuracy when device is still. Default: true */
|
|
39
|
+
stopWhenStill?: boolean;
|
|
40
|
+
/** Filter strategy: 'interval', 'distance', or 'both'. Default: 'both' */
|
|
41
|
+
mode?: OptimizationMode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Android foreground service notification configuration.
|
|
46
|
+
*/
|
|
47
|
+
export interface AndroidNotificationConfig {
|
|
48
|
+
/** Whether to show the foreground service notification. Default: true */
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
/** Notification title */
|
|
51
|
+
title: string;
|
|
52
|
+
/** Notification body text */
|
|
53
|
+
text: string;
|
|
54
|
+
/** Notification icon resource name */
|
|
55
|
+
icon?: string;
|
|
56
|
+
/** Notification channel ID */
|
|
57
|
+
channelId?: string;
|
|
58
|
+
/** Notification channel name */
|
|
59
|
+
channelName?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* iOS persistent notification configuration.
|
|
64
|
+
* Shows a local notification while tracking is active (similar to Android's foreground service notification).
|
|
65
|
+
*/
|
|
66
|
+
export interface IOSNotificationConfig {
|
|
67
|
+
/** Whether to show the persistent local notification. Default: true */
|
|
68
|
+
enabled?: boolean;
|
|
69
|
+
/** Notification title */
|
|
70
|
+
title: string;
|
|
71
|
+
/** Notification body text */
|
|
72
|
+
text: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A user-defined sync target specifying a Firebase path, write method,
|
|
77
|
+
* and optional batching/offline queue settings.
|
|
78
|
+
*/
|
|
79
|
+
export interface SyncTarget {
|
|
80
|
+
/** Firebase path to write to */
|
|
81
|
+
path: string;
|
|
82
|
+
/** Write method: 'set' (overwrite), 'push' (append), 'update' (merge) */
|
|
83
|
+
method: 'set' | 'push' | 'update';
|
|
84
|
+
/** Number of points to accumulate before writing. Default: 1 (immediate) */
|
|
85
|
+
batchSize?: number;
|
|
86
|
+
/** Whether to persist data offline when device has no connectivity */
|
|
87
|
+
offlineQueue?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Firebase connection and sync target configuration.
|
|
92
|
+
*/
|
|
93
|
+
export interface FirebaseConfig {
|
|
94
|
+
/** Firebase service type: Realtime Database or Firestore */
|
|
95
|
+
service: 'RTDB' | 'Firestore';
|
|
96
|
+
/** Array of sync targets (at least one required) */
|
|
97
|
+
targets: [SyncTarget, ...SyncTarget[]];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Main configuration object passed to `configure()`.
|
|
102
|
+
*/
|
|
103
|
+
export interface TrackingConfig {
|
|
104
|
+
/** Optimization settings for battery and update frequency */
|
|
105
|
+
optimization: OptimizationConfig;
|
|
106
|
+
/** Android foreground service notification settings (Android only) */
|
|
107
|
+
androidNotification?: AndroidNotificationConfig;
|
|
108
|
+
/** iOS persistent notification settings (iOS only) */
|
|
109
|
+
iosNotification?: IOSNotificationConfig;
|
|
110
|
+
/** Firebase connection and path configuration */
|
|
111
|
+
firebase: FirebaseConfig;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Data Interfaces ─────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Location data emitted by the library on each valid location update.
|
|
118
|
+
*/
|
|
119
|
+
export interface LocationData {
|
|
120
|
+
/** Latitude in degrees (-90 to 90) */
|
|
121
|
+
latitude: number;
|
|
122
|
+
/** Longitude in degrees (-180 to 180) */
|
|
123
|
+
longitude: number;
|
|
124
|
+
/** Unix timestamp in milliseconds */
|
|
125
|
+
timestamp: number;
|
|
126
|
+
/** Horizontal accuracy in meters */
|
|
127
|
+
accuracy: number;
|
|
128
|
+
/** Speed in meters per second, or null if unavailable */
|
|
129
|
+
speed: number | null;
|
|
130
|
+
/** Altitude in meters above sea level, or null if unavailable */
|
|
131
|
+
altitude: number | null;
|
|
132
|
+
/** Bearing/heading in degrees (0-360), or null if unavailable */
|
|
133
|
+
bearing: number | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Error & Status Interfaces ───────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Error object emitted by the library when tracking issues occur.
|
|
140
|
+
*/
|
|
141
|
+
export interface TrackingError {
|
|
142
|
+
/** Error code identifier (e.g., 'PERMISSION_DENIED', 'GPS_DISABLED') */
|
|
143
|
+
code: string;
|
|
144
|
+
/** Human-readable error message */
|
|
145
|
+
message: string;
|
|
146
|
+
/** Whether the error is recoverable (auto-retry or user action can fix) */
|
|
147
|
+
recoverable: boolean;
|
|
148
|
+
/** Additional error context */
|
|
149
|
+
details?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Current tracking status snapshot.
|
|
154
|
+
*/
|
|
155
|
+
export interface TrackingStatus {
|
|
156
|
+
/** Current state of the tracking lifecycle */
|
|
157
|
+
state: TrackingState;
|
|
158
|
+
/** Whether the device currently has network connectivity */
|
|
159
|
+
isOnline: boolean;
|
|
160
|
+
/** Number of locations waiting in the offline queue */
|
|
161
|
+
queuedLocations: number;
|
|
162
|
+
/** Last known location, or null if no location has been received */
|
|
163
|
+
lastLocation: LocationData | null;
|
|
164
|
+
/** Current battery optimization mode */
|
|
165
|
+
batteryOptimization: 'full_accuracy' | 'low_power' | 'disabled';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Validation Interfaces ───────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Single configuration validation error.
|
|
172
|
+
*/
|
|
173
|
+
export interface ConfigError {
|
|
174
|
+
/** Dot-notation path to the invalid field (e.g., 'firebase.service') */
|
|
175
|
+
field: string;
|
|
176
|
+
/** Human-readable description of the validation error */
|
|
177
|
+
message: string;
|
|
178
|
+
/** Machine-readable error code */
|
|
179
|
+
code: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Result of configuration validation.
|
|
184
|
+
*/
|
|
185
|
+
export interface ConfigValidationResult {
|
|
186
|
+
/** Whether the configuration is valid */
|
|
187
|
+
valid: boolean;
|
|
188
|
+
/** List of validation errors (empty if valid) */
|
|
189
|
+
errors: ConfigError[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Utility Types ───────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Subscription handle returned by event listener registrations.
|
|
196
|
+
* Call `remove()` to unsubscribe from the event.
|
|
197
|
+
*/
|
|
198
|
+
export interface Subscription {
|
|
199
|
+
/** Unsubscribe from the event */
|
|
200
|
+
remove(): void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Module Interface ────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Main public API interface for the react-native-live-tracking library.
|
|
207
|
+
*/
|
|
208
|
+
export interface LiveTrackingModule {
|
|
209
|
+
/**
|
|
210
|
+
* Configure the tracking library with the given parameters.
|
|
211
|
+
* Must be called before `start()`.
|
|
212
|
+
*/
|
|
213
|
+
configure(config: TrackingConfig): Promise<void>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Start location tracking. Requires `configure()` to have been called first.
|
|
217
|
+
*/
|
|
218
|
+
start(): Promise<void>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Stop location tracking and clean up resources.
|
|
222
|
+
*/
|
|
223
|
+
stop(): Promise<void>;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Register a callback for location updates.
|
|
227
|
+
* Returns a Subscription that can be used to unsubscribe.
|
|
228
|
+
*/
|
|
229
|
+
onLocationUpdate(callback: (location: LocationData) => void): Subscription;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Register a callback for tracking errors.
|
|
233
|
+
* Returns a Subscription that can be used to unsubscribe.
|
|
234
|
+
*/
|
|
235
|
+
onError(callback: (error: TrackingError) => void): Subscription;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get the current tracking status.
|
|
239
|
+
*/
|
|
240
|
+
getStatus(): Promise<TrackingStatus>;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get the number of locations currently queued for sync.
|
|
244
|
+
*/
|
|
245
|
+
getQueuedLocations(): Promise<number>;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get the number of queued locations per target path.
|
|
249
|
+
* Returns a record mapping each configured target path to its queued location count.
|
|
250
|
+
*/
|
|
251
|
+
getQueuedLocationsByTarget(): Promise<Record<string, number>>;
|
|
252
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distance calculation utility using the Haversine formula.
|
|
3
|
+
*
|
|
4
|
+
* The Haversine formula determines the great-circle distance between two points
|
|
5
|
+
* on a sphere given their longitudes and latitudes. This is used by the
|
|
6
|
+
* Distance/Time Matrix filter to determine if a new location update has moved
|
|
7
|
+
* far enough from the last known position.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Earth's mean radius in meters.
|
|
14
|
+
* Used as the sphere radius in the Haversine formula.
|
|
15
|
+
*/
|
|
16
|
+
export const EARTH_RADIUS_METERS = 6371000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Converts degrees to radians.
|
|
20
|
+
*
|
|
21
|
+
* @param degrees - Angle in degrees
|
|
22
|
+
* @returns Angle in radians
|
|
23
|
+
*/
|
|
24
|
+
function toRadians(degrees: number): number {
|
|
25
|
+
return degrees * (Math.PI / 180);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculates the distance in meters between two geographic coordinates
|
|
30
|
+
* using the Haversine formula.
|
|
31
|
+
*
|
|
32
|
+
* The Haversine formula accounts for the curvature of the Earth and provides
|
|
33
|
+
* accurate results for short and medium distances. It assumes a spherical Earth
|
|
34
|
+
* with a mean radius of 6,371,000 meters.
|
|
35
|
+
*
|
|
36
|
+
* @param lat1 - Latitude of the first point in degrees (-90 to 90)
|
|
37
|
+
* @param lng1 - Longitude of the first point in degrees (-180 to 180)
|
|
38
|
+
* @param lat2 - Latitude of the second point in degrees (-90 to 90)
|
|
39
|
+
* @param lng2 - Longitude of the second point in degrees (-180 to 180)
|
|
40
|
+
* @returns Distance between the two points in meters
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // Distance between Jakarta and Bandung (approximately 120 km)
|
|
45
|
+
* const distance = calculateDistance(-6.2088, 106.8456, -6.9175, 107.6191);
|
|
46
|
+
* console.log(distance); // ~120,000 meters
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function calculateDistance(
|
|
50
|
+
lat1: number,
|
|
51
|
+
lng1: number,
|
|
52
|
+
lat2: number,
|
|
53
|
+
lng2: number
|
|
54
|
+
): number {
|
|
55
|
+
const dLat = toRadians(lat2 - lat1);
|
|
56
|
+
const dLng = toRadians(lng2 - lng1);
|
|
57
|
+
|
|
58
|
+
const a =
|
|
59
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
60
|
+
Math.cos(toRadians(lat1)) *
|
|
61
|
+
Math.cos(toRadians(lat2)) *
|
|
62
|
+
Math.sin(dLng / 2) *
|
|
63
|
+
Math.sin(dLng / 2);
|
|
64
|
+
|
|
65
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
66
|
+
|
|
67
|
+
return EARTH_RADIUS_METERS * c;
|
|
68
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exponential backoff retry utilities for Firebase write operations.
|
|
3
|
+
*
|
|
4
|
+
* Used by the Firebase Sync Engine to determine delay between retry attempts
|
|
5
|
+
* when writes fail due to network issues or transient errors.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/retry
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Default base delay in milliseconds for exponential backoff */
|
|
11
|
+
export const DEFAULT_BASE_DELAY = 1000;
|
|
12
|
+
|
|
13
|
+
/** Default maximum jitter in milliseconds (±) to avoid thundering herd */
|
|
14
|
+
export const DEFAULT_MAX_JITTER = 200;
|
|
15
|
+
|
|
16
|
+
/** Maximum retry attempts for sync targets using 'set' or 'update' write methods */
|
|
17
|
+
export const MAX_RETRIES_SET_UPDATE = 3;
|
|
18
|
+
|
|
19
|
+
/** Maximum retry attempts for sync targets using 'push' write method */
|
|
20
|
+
export const MAX_RETRIES_PUSH = 5;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Calculates the delay before the next retry attempt using exponential backoff
|
|
24
|
+
* with random jitter.
|
|
25
|
+
*
|
|
26
|
+
* Formula: delay = baseDelay × 2^(attempt - 1) + random(-maxJitter, +maxJitter)
|
|
27
|
+
*
|
|
28
|
+
* The result is clamped to a minimum of 0 to prevent negative delays.
|
|
29
|
+
*
|
|
30
|
+
* @param attempt - The retry attempt number (1-indexed, first retry = 1)
|
|
31
|
+
* @param baseDelay - The base delay in milliseconds (default: 1000ms)
|
|
32
|
+
* @param maxJitter - The maximum jitter offset in milliseconds (default: 200ms).
|
|
33
|
+
* A random value between -maxJitter and +maxJitter is added to the delay.
|
|
34
|
+
* @returns The calculated delay in milliseconds (never negative)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* // First retry: ~1000ms ± 200ms
|
|
39
|
+
* const delay1 = calculateBackoffDelay(1);
|
|
40
|
+
*
|
|
41
|
+
* // Second retry: ~2000ms ± 200ms
|
|
42
|
+
* const delay2 = calculateBackoffDelay(2);
|
|
43
|
+
*
|
|
44
|
+
* // Third retry: ~4000ms ± 200ms
|
|
45
|
+
* const delay3 = calculateBackoffDelay(3);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function calculateBackoffDelay(
|
|
49
|
+
attempt: number,
|
|
50
|
+
baseDelay: number = DEFAULT_BASE_DELAY,
|
|
51
|
+
maxJitter: number = DEFAULT_MAX_JITTER
|
|
52
|
+
): number {
|
|
53
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
54
|
+
const jitter = (Math.random() * 2 - 1) * maxJitter;
|
|
55
|
+
return Math.max(0, exponentialDelay + jitter);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines whether a retry should be attempted based on the current attempt
|
|
60
|
+
* number and the maximum allowed retries.
|
|
61
|
+
*
|
|
62
|
+
* @param attempt - The current attempt number (1-indexed)
|
|
63
|
+
* @param maxRetries - The maximum number of retries allowed
|
|
64
|
+
* @returns `true` if the attempt is within the allowed retry limit, `false` otherwise
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* shouldRetry(1, 3); // true - first retry is allowed
|
|
69
|
+
* shouldRetry(3, 3); // true - third retry is allowed
|
|
70
|
+
* shouldRetry(4, 3); // false - exceeds max retries
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function shouldRetry(attempt: number, maxRetries: number): boolean {
|
|
74
|
+
return attempt <= maxRetries;
|
|
75
|
+
}
|