expo-background-tracking 1.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.
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.HttpService = void 0;
37
+ const Network = __importStar(require("expo-network"));
38
+ const Database_1 = require("./Database");
39
+ const Logger_1 = require("./Logger");
40
+ /**
41
+ * HTTP upload service: autoSync, batchSync, templates, JWT refresh,
42
+ * offline queue with re-sync on reconnect. Pure `fetch` — no Firebase.
43
+ */
44
+ class HttpService {
45
+ constructor(bus) {
46
+ this.bus = bus;
47
+ this.config = {};
48
+ this.syncing = false;
49
+ this.connectivityTimer = null;
50
+ this.lastConnected = null;
51
+ }
52
+ setConfig(config) {
53
+ this.config = config;
54
+ }
55
+ startConnectivityMonitoring(intervalMs = 10000) {
56
+ this.stopConnectivityMonitoring();
57
+ this.connectivityTimer = setInterval(() => {
58
+ void this.checkConnectivity();
59
+ }, intervalMs);
60
+ void this.checkConnectivity();
61
+ }
62
+ stopConnectivityMonitoring() {
63
+ if (this.connectivityTimer) {
64
+ clearInterval(this.connectivityTimer);
65
+ this.connectivityTimer = null;
66
+ }
67
+ }
68
+ async checkConnectivity() {
69
+ try {
70
+ const state = await Network.getNetworkStateAsync();
71
+ const connected = !!(state.isConnected && state.isInternetReachable !== false);
72
+ if (this.lastConnected !== null && connected !== this.lastConnected) {
73
+ this.bus.emit('connectivitychange', { connected });
74
+ if (connected && this.config.autoSync !== false) {
75
+ void this.sync();
76
+ }
77
+ }
78
+ this.lastConnected = connected;
79
+ }
80
+ catch {
81
+ /* ignore */
82
+ }
83
+ }
84
+ /** Called after each persisted location when autoSync is enabled. */
85
+ async autoSync() {
86
+ if (!this.config.url || this.config.autoSync === false)
87
+ return;
88
+ const threshold = this.config.autoSyncThreshold ?? 0;
89
+ if (threshold > 0) {
90
+ const count = await Database_1.database.getCount();
91
+ if (count < threshold)
92
+ return;
93
+ }
94
+ await this.sync();
95
+ }
96
+ /** Upload all queued locations. Resolves with the uploaded records. */
97
+ async sync() {
98
+ if (!this.config.url) {
99
+ throw new Error('sync() requires config.url');
100
+ }
101
+ if (this.syncing)
102
+ return [];
103
+ this.syncing = true;
104
+ try {
105
+ const queue = await Database_1.database.getLocations({ order: 1 });
106
+ if (!queue.length)
107
+ return [];
108
+ const batchSync = this.config.batchSync === true;
109
+ const maxBatch = this.config.maxBatchSize && this.config.maxBatchSize > 0
110
+ ? this.config.maxBatchSize
111
+ : Infinity;
112
+ if (batchSync) {
113
+ let i = 0;
114
+ while (i < queue.length) {
115
+ const batch = queue.slice(i, i + (isFinite(maxBatch) ? maxBatch : queue.length));
116
+ const ok = await this.upload(batch);
117
+ if (!ok)
118
+ break;
119
+ await Database_1.database.deleteLocations(batch.map((l) => l.uuid));
120
+ i += batch.length;
121
+ }
122
+ }
123
+ else {
124
+ for (const location of queue) {
125
+ const ok = await this.upload([location]);
126
+ if (!ok)
127
+ break;
128
+ await Database_1.database.destroyLocation(location.uuid);
129
+ }
130
+ }
131
+ return queue;
132
+ }
133
+ finally {
134
+ this.syncing = false;
135
+ }
136
+ }
137
+ renderTemplate(template, location) {
138
+ return template.replace(/<%=?\s*([\w.]+)\s*%>/g, (_m, path) => {
139
+ const flat = {
140
+ ...location.coords,
141
+ uuid: location.uuid,
142
+ timestamp: location.timestamp,
143
+ odometer: location.odometer,
144
+ is_moving: location.is_moving,
145
+ activity: location.activity?.activity,
146
+ 'activity.type': location.activity?.activity,
147
+ 'activity.confidence': location.activity?.confidence,
148
+ 'battery.level': location.battery?.level,
149
+ 'battery.is_charging': location.battery?.is_charging,
150
+ };
151
+ const v = flat[path];
152
+ return v === undefined ? 'null' : JSON.stringify(v);
153
+ });
154
+ }
155
+ buildBody(locations) {
156
+ const template = this.config.locationTemplate;
157
+ const extras = this.config.extras ?? {};
158
+ const records = locations.map((l) => {
159
+ if (template) {
160
+ try {
161
+ return JSON.parse(this.renderTemplate(template, l));
162
+ }
163
+ catch {
164
+ return { ...l, extras: { ...extras, ...l.extras } };
165
+ }
166
+ }
167
+ return { ...l, extras: { ...extras, ...l.extras } };
168
+ });
169
+ const root = this.config.httpRootProperty ?? 'location';
170
+ const payload = locations.length === 1 && !this.config.batchSync ? records[0] : records;
171
+ if (root === '.')
172
+ return payload;
173
+ return { [root]: payload, ...(this.config.params ?? {}) };
174
+ }
175
+ async authHeaders() {
176
+ const auth = this.config.authorization;
177
+ if (!auth?.accessToken)
178
+ return {};
179
+ return { Authorization: `Bearer ${auth.accessToken}` };
180
+ }
181
+ async refreshToken(auth) {
182
+ if (!auth.refreshUrl || !auth.refreshToken)
183
+ return false;
184
+ try {
185
+ const payload = {};
186
+ const tpl = auth.refreshPayload ?? { refresh_token: '{refreshToken}' };
187
+ for (const [k, v] of Object.entries(tpl)) {
188
+ payload[k] = v.replace('{refreshToken}', auth.refreshToken);
189
+ }
190
+ const res = await fetch(auth.refreshUrl, {
191
+ method: 'POST',
192
+ headers: { 'Content-Type': 'application/json', ...(auth.refreshHeaders ?? {}) },
193
+ body: JSON.stringify(payload),
194
+ });
195
+ const json = (await res.json().catch(() => ({})));
196
+ const event = res.ok
197
+ ? { success: true, status: res.status, response: json }
198
+ : { success: false, status: res.status, error: 'refresh failed' };
199
+ if (res.ok) {
200
+ const token = json.access_token ?? json.accessToken ?? null;
201
+ const refresh = json.refresh_token ?? json.refreshToken ?? null;
202
+ if (token)
203
+ auth.accessToken = token;
204
+ if (refresh)
205
+ auth.refreshToken = refresh;
206
+ }
207
+ this.bus.emit('authorization', event);
208
+ return res.ok;
209
+ }
210
+ catch (e) {
211
+ this.bus.emit('authorization', {
212
+ success: false,
213
+ error: String(e),
214
+ });
215
+ return false;
216
+ }
217
+ }
218
+ async upload(locations, retried = false) {
219
+ const url = this.config.url;
220
+ const method = this.config.method ?? 'POST';
221
+ const timeout = this.config.httpTimeout ?? 60000;
222
+ const controller = new AbortController();
223
+ const timer = setTimeout(() => controller.abort(), timeout);
224
+ try {
225
+ const res = await fetch(url, {
226
+ method,
227
+ headers: {
228
+ 'Content-Type': 'application/json',
229
+ ...(this.config.headers ?? {}),
230
+ ...(await this.authHeaders()),
231
+ },
232
+ body: JSON.stringify(this.buildBody(locations)),
233
+ signal: controller.signal,
234
+ });
235
+ const text = await res.text().catch(() => '');
236
+ const event = {
237
+ success: res.ok,
238
+ status: res.status,
239
+ responseText: text,
240
+ };
241
+ this.bus.emit('http', event);
242
+ if (res.status === 401 && !retried && this.config.authorization) {
243
+ const refreshed = await this.refreshToken(this.config.authorization);
244
+ if (refreshed)
245
+ return this.upload(locations, true);
246
+ }
247
+ if (!res.ok)
248
+ Logger_1.logger.warn(`HTTP ${res.status}: ${text.slice(0, 200)}`);
249
+ return res.ok;
250
+ }
251
+ catch (e) {
252
+ Logger_1.logger.warn(`HTTP error: ${String(e)}`);
253
+ this.bus.emit('http', {
254
+ success: false,
255
+ status: 0,
256
+ responseText: String(e),
257
+ });
258
+ return false;
259
+ }
260
+ finally {
261
+ clearTimeout(timer);
262
+ }
263
+ }
264
+ }
265
+ exports.HttpService = HttpService;
@@ -0,0 +1,15 @@
1
+ import { LogLevel } from './types';
2
+ /** SQLite-backed logger, mirrors BackgroundGeolocation.logger. */
3
+ export declare class Logger {
4
+ level: LogLevel;
5
+ debug: boolean;
6
+ private write;
7
+ error(message: string): void;
8
+ warn(message: string): void;
9
+ info(message: string): void;
10
+ debugLog(message: string): void;
11
+ notice(message: string): void;
12
+ getLog(): Promise<string>;
13
+ destroyLog(): Promise<void>;
14
+ }
15
+ export declare const logger: Logger;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = exports.Logger = void 0;
4
+ const Database_1 = require("./Database");
5
+ const types_1 = require("./types");
6
+ /** SQLite-backed logger, mirrors BackgroundGeolocation.logger. */
7
+ class Logger {
8
+ constructor() {
9
+ this.level = types_1.LogLevel.INFO;
10
+ this.debug = false;
11
+ }
12
+ write(level, tag, message) {
13
+ if (level > this.level)
14
+ return;
15
+ const line = `[${tag}] ${message}`;
16
+ if (this.debug || level <= types_1.LogLevel.WARNING) {
17
+ const fn = level === types_1.LogLevel.ERROR
18
+ ? console.error
19
+ : level === types_1.LogLevel.WARNING
20
+ ? console.warn
21
+ : console.log;
22
+ fn(`[expo-background-tracking]${line}`);
23
+ }
24
+ Database_1.database.insertLog(types_1.LogLevel[level], line).catch(() => undefined);
25
+ }
26
+ error(message) {
27
+ this.write(types_1.LogLevel.ERROR, 'ERROR', message);
28
+ }
29
+ warn(message) {
30
+ this.write(types_1.LogLevel.WARNING, 'WARN', message);
31
+ }
32
+ info(message) {
33
+ this.write(types_1.LogLevel.INFO, 'INFO', message);
34
+ }
35
+ debugLog(message) {
36
+ this.write(types_1.LogLevel.DEBUG, 'DEBUG', message);
37
+ }
38
+ notice(message) {
39
+ this.write(types_1.LogLevel.INFO, 'NOTICE', message);
40
+ }
41
+ async getLog() {
42
+ const entries = await Database_1.database.getLogs();
43
+ return entries
44
+ .map((e) => `${e.timestamp} ${e.level} ${e.message}`)
45
+ .join('\n');
46
+ }
47
+ async destroyLog() {
48
+ await Database_1.database.destroyLogs();
49
+ }
50
+ }
51
+ exports.Logger = Logger;
52
+ exports.logger = new Logger();
@@ -0,0 +1,40 @@
1
+ import type { ActivityChangeEvent } from './types';
2
+ export interface MotionDetectorOptions {
3
+ /** Minutes of stillness before declaring stationary (stopTimeout). */
4
+ stopTimeoutMinutes: number;
5
+ /** ms of sustained motion before declaring moving (motionTriggerDelay). */
6
+ motionTriggerDelayMs: number;
7
+ /** Disable accelerometer sampling (disableMotionActivityUpdates). */
8
+ disabled: boolean;
9
+ onMotionChange: (isMoving: boolean) => void;
10
+ onActivityChange: (activity: ActivityChangeEvent) => void;
11
+ }
12
+ /**
13
+ * Battery-conscious motion intelligence:
14
+ * - accelerometer variance → moving / stationary transitions
15
+ * - GPS speed + accelerometer → activity classification
16
+ * (still, on_foot, walking, running, on_bicycle, in_vehicle)
17
+ */
18
+ export declare class MotionDetector {
19
+ private sub;
20
+ private samples;
21
+ private isMoving;
22
+ private stillSince;
23
+ private movingSince;
24
+ private lastActivity;
25
+ private lastSpeed;
26
+ private opts;
27
+ private static MOTION_THRESHOLD;
28
+ constructor(opts: MotionDetectorOptions);
29
+ setOptions(opts: Partial<MotionDetectorOptions>): void;
30
+ get moving(): boolean;
31
+ /** Force pace (changePace). */
32
+ setMoving(isMoving: boolean): void;
33
+ /** Feed GPS speed (m/s) for activity classification. */
34
+ feedSpeed(speed: number | null): void;
35
+ start(): Promise<void>;
36
+ stop(): void;
37
+ private evaluate;
38
+ private noteMotion;
39
+ private classify;
40
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MotionDetector = void 0;
4
+ const expo_sensors_1 = require("expo-sensors");
5
+ /**
6
+ * Battery-conscious motion intelligence:
7
+ * - accelerometer variance → moving / stationary transitions
8
+ * - GPS speed + accelerometer → activity classification
9
+ * (still, on_foot, walking, running, on_bicycle, in_vehicle)
10
+ */
11
+ class MotionDetector {
12
+ constructor(opts) {
13
+ this.sub = null;
14
+ this.samples = [];
15
+ this.isMoving = false;
16
+ this.stillSince = null;
17
+ this.movingSince = null;
18
+ this.lastActivity = 'still';
19
+ this.lastSpeed = 0;
20
+ this.opts = opts;
21
+ }
22
+ setOptions(opts) {
23
+ this.opts = { ...this.opts, ...opts };
24
+ }
25
+ get moving() {
26
+ return this.isMoving;
27
+ }
28
+ /** Force pace (changePace). */
29
+ setMoving(isMoving) {
30
+ if (this.isMoving === isMoving)
31
+ return;
32
+ this.isMoving = isMoving;
33
+ this.stillSince = null;
34
+ this.movingSince = null;
35
+ this.opts.onMotionChange(isMoving);
36
+ }
37
+ /** Feed GPS speed (m/s) for activity classification. */
38
+ feedSpeed(speed) {
39
+ if (speed == null || speed < 0)
40
+ return;
41
+ this.lastSpeed = speed;
42
+ this.classify();
43
+ // GPS speed is also strong evidence of motion
44
+ if (speed > 1 && !this.isMoving)
45
+ this.noteMotion(true);
46
+ }
47
+ async start() {
48
+ if (this.opts.disabled)
49
+ return;
50
+ try {
51
+ const available = await expo_sensors_1.Accelerometer.isAvailableAsync();
52
+ if (!available)
53
+ return;
54
+ }
55
+ catch {
56
+ return;
57
+ }
58
+ expo_sensors_1.Accelerometer.setUpdateInterval(1000);
59
+ this.sub = expo_sensors_1.Accelerometer.addListener(({ x, y, z }) => {
60
+ const magnitude = Math.abs(Math.sqrt(x * x + y * y + z * z) - 1); // gravity-normalized
61
+ this.samples.push(magnitude);
62
+ if (this.samples.length > 10)
63
+ this.samples.shift();
64
+ if (this.samples.length >= 5)
65
+ this.evaluate();
66
+ });
67
+ }
68
+ stop() {
69
+ this.sub?.remove();
70
+ this.sub = null;
71
+ this.samples = [];
72
+ this.stillSince = null;
73
+ this.movingSince = null;
74
+ }
75
+ evaluate() {
76
+ const mean = this.samples.reduce((a, b) => a + b, 0) / this.samples.length;
77
+ const variance = this.samples.reduce((a, b) => a + (b - mean) ** 2, 0) / this.samples.length;
78
+ const active = variance > MotionDetector.MOTION_THRESHOLD ||
79
+ mean > MotionDetector.MOTION_THRESHOLD * 2;
80
+ this.noteMotion(active);
81
+ this.classify();
82
+ }
83
+ noteMotion(active) {
84
+ const now = Date.now();
85
+ if (active) {
86
+ this.stillSince = null;
87
+ if (!this.isMoving) {
88
+ if (this.movingSince == null)
89
+ this.movingSince = now;
90
+ if (now - this.movingSince >= this.opts.motionTriggerDelayMs) {
91
+ this.isMoving = true;
92
+ this.movingSince = null;
93
+ this.opts.onMotionChange(true);
94
+ }
95
+ }
96
+ }
97
+ else {
98
+ this.movingSince = null;
99
+ if (this.isMoving) {
100
+ if (this.stillSince == null)
101
+ this.stillSince = now;
102
+ if (now - this.stillSince >= this.opts.stopTimeoutMinutes * 60000) {
103
+ this.isMoving = false;
104
+ this.stillSince = null;
105
+ this.opts.onMotionChange(false);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ classify() {
111
+ const speed = this.lastSpeed;
112
+ let activity;
113
+ let confidence = 75;
114
+ if (!this.isMoving && speed < 0.5) {
115
+ activity = 'still';
116
+ confidence = 90;
117
+ }
118
+ else if (speed < 2) {
119
+ activity = 'walking';
120
+ }
121
+ else if (speed < 3.5) {
122
+ activity = 'running';
123
+ confidence = 60;
124
+ }
125
+ else if (speed < 8) {
126
+ activity = 'on_bicycle';
127
+ confidence = 55;
128
+ }
129
+ else {
130
+ activity = 'in_vehicle';
131
+ confidence = 85;
132
+ }
133
+ if (activity !== this.lastActivity) {
134
+ this.lastActivity = activity;
135
+ this.opts.onActivityChange({ activity, confidence });
136
+ }
137
+ }
138
+ }
139
+ exports.MotionDetector = MotionDetector;
140
+ // m/s² variance thresholds (device acceleration, gravity removed approx.)
141
+ MotionDetector.MOTION_THRESHOLD = 0.05;
@@ -0,0 +1,19 @@
1
+ import type { ScheduleItem } from './types';
2
+ /**
3
+ * Weekly schedule engine — "1-5 09:00-17:00" style entries
4
+ * (same syntax as transistorsoft). JS timer-based: evaluated every
5
+ * 30 s while the app runs; state re-evaluated on resume.
6
+ */
7
+ export declare class Scheduler {
8
+ private onScheduleChange;
9
+ private timer;
10
+ private schedules;
11
+ private lastShouldBeEnabled;
12
+ constructor(onScheduleChange: (enabled: boolean) => void);
13
+ get enabled(): boolean;
14
+ setSchedule(items: ScheduleItem[]): void;
15
+ start(): void;
16
+ stop(): void;
17
+ private evaluate;
18
+ private parse;
19
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scheduler = void 0;
4
+ const Logger_1 = require("./Logger");
5
+ /**
6
+ * Weekly schedule engine — "1-5 09:00-17:00" style entries
7
+ * (same syntax as transistorsoft). JS timer-based: evaluated every
8
+ * 30 s while the app runs; state re-evaluated on resume.
9
+ */
10
+ class Scheduler {
11
+ constructor(onScheduleChange) {
12
+ this.onScheduleChange = onScheduleChange;
13
+ this.timer = null;
14
+ this.schedules = [];
15
+ this.lastShouldBeEnabled = null;
16
+ }
17
+ get enabled() {
18
+ return this.timer != null;
19
+ }
20
+ setSchedule(items) {
21
+ this.schedules = items
22
+ .map((s) => this.parse(s))
23
+ .filter((s) => s != null);
24
+ }
25
+ start() {
26
+ if (this.timer)
27
+ return;
28
+ if (!this.schedules.length) {
29
+ Logger_1.logger.warn('startSchedule: no schedule configured');
30
+ return;
31
+ }
32
+ this.timer = setInterval(() => this.evaluate(), 30000);
33
+ this.evaluate();
34
+ Logger_1.logger.info('Schedule started');
35
+ }
36
+ stop() {
37
+ if (this.timer) {
38
+ clearInterval(this.timer);
39
+ this.timer = null;
40
+ }
41
+ this.lastShouldBeEnabled = null;
42
+ Logger_1.logger.info('Schedule stopped');
43
+ }
44
+ evaluate() {
45
+ const now = new Date();
46
+ const isoDay = now.getDay() === 0 ? 7 : now.getDay();
47
+ const minutes = now.getHours() * 60 + now.getMinutes();
48
+ const shouldBeEnabled = this.schedules.some((s) => s.days.has(isoDay) && minutes >= s.startMinutes && minutes < s.endMinutes);
49
+ if (shouldBeEnabled !== this.lastShouldBeEnabled) {
50
+ this.lastShouldBeEnabled = shouldBeEnabled;
51
+ this.onScheduleChange(shouldBeEnabled);
52
+ }
53
+ }
54
+ parse(item) {
55
+ // "1-5 09:00-17:00" | "7 10:00-12:00" | "1,3,5 08:30-18:00"
56
+ const m = item.trim().match(/^([\d,\-]+)\s+(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})$/);
57
+ if (!m) {
58
+ Logger_1.logger.warn(`Invalid schedule item: "${item}"`);
59
+ return null;
60
+ }
61
+ const days = new Set();
62
+ for (const part of m[1].split(',')) {
63
+ const range = part.split('-').map(Number);
64
+ if (range.length === 2) {
65
+ for (let d = range[0]; d <= range[1]; d++)
66
+ days.add(d);
67
+ }
68
+ else {
69
+ days.add(range[0]);
70
+ }
71
+ }
72
+ return {
73
+ days,
74
+ startMinutes: Number(m[2]) * 60 + Number(m[3]),
75
+ endMinutes: Number(m[4]) * 60 + Number(m[5]),
76
+ };
77
+ }
78
+ }
79
+ exports.Scheduler = Scheduler;
@@ -0,0 +1,8 @@
1
+ /** True when running inside the Expo Go sandbox app. */
2
+ export declare function isExpoGo(): boolean;
3
+ /**
4
+ * Background location (expo-task-manager location tasks) requires a
5
+ * development build — it is not available inside Expo Go.
6
+ * https://docs.expo.dev/versions/latest/sdk/location/
7
+ */
8
+ export declare function isBackgroundCapable(): boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isExpoGo = isExpoGo;
7
+ exports.isBackgroundCapable = isBackgroundCapable;
8
+ const expo_constants_1 = __importDefault(require("expo-constants"));
9
+ /** True when running inside the Expo Go sandbox app. */
10
+ function isExpoGo() {
11
+ try {
12
+ return (expo_constants_1.default
13
+ ?.executionEnvironment === 'storeClient');
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ /**
20
+ * Background location (expo-task-manager location tasks) requires a
21
+ * development build — it is not available inside Expo Go.
22
+ * https://docs.expo.dev/versions/latest/sdk/location/
23
+ */
24
+ function isBackgroundCapable() {
25
+ return !isExpoGo();
26
+ }
package/build/geo.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** Geodesic helpers. */
2
+ export declare function haversine(lat1: number, lon1: number, lat2: number, lon2: number): number;
3
+ /** Point-in-polygon (ray casting) — vertices as [lat, lon]. */
4
+ export declare function pointInPolygon(lat: number, lon: number, vertices: Array<[number, number]>): boolean;
5
+ /** Centroid of a polygon's vertices ([lat, lon]). */
6
+ export declare function centroid(vertices: Array<[number, number]>): {
7
+ latitude: number;
8
+ longitude: number;
9
+ };
10
+ export declare function uuidv4(): string;