Skip to content

Commit a132a09

Browse files
authored
Merge pull request #684 from whiteghost0001/feature/mobile-zk-auth-and-geofencing
feat(mobile): implement ZK-SNARK auth (#584) and background geofencin…
2 parents 4578f75 + 56aa696 commit a132a09

4 files changed

Lines changed: 1001 additions & 0 deletions

File tree

mobile/src/hooks/useGeofencing.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* useGeofencing — React hook for background geofencing integration
3+
*
4+
* Issue #589: [Mobile] Implement Background Location Geofencing for IRL Events
5+
*
6+
* Usage:
7+
* const { status, transitions, start, stop } = useGeofencing(regions);
8+
*
9+
* - Manages the full lifecycle: permissions → start → monitor → stop
10+
* - Exposes a live list of geofence transitions for in-app UI
11+
* - Cleans up the background task on unmount
12+
*/
13+
14+
import { useCallback, useEffect, useRef, useState } from "react";
15+
import {
16+
GeofenceRegion,
17+
GeofenceTransition,
18+
GeofencingStatus,
19+
getGeofencingStatus,
20+
startGeofencing,
21+
stopGeofencing,
22+
} from "../services/GeofencingService";
23+
24+
// ─── Types ────────────────────────────────────────────────────────────────────
25+
26+
export type GeofencingHookState =
27+
| "idle"
28+
| "requesting_permissions"
29+
| "active"
30+
| "stopped"
31+
| "error";
32+
33+
export interface UseGeofencingReturn {
34+
/** Current hook lifecycle state */
35+
hookState: GeofencingHookState;
36+
/** Detailed status from the native service */
37+
serviceStatus: GeofencingStatus | null;
38+
/** Ordered list of transitions (newest first, capped at 50) */
39+
transitions: GeofenceTransition[];
40+
/** Most recent transition, or null */
41+
latestTransition: GeofenceTransition | null;
42+
/** Start monitoring the supplied regions */
43+
start: () => Promise<void>;
44+
/** Stop monitoring and clean up */
45+
stop: () => Promise<void>;
46+
/** Clear the transitions history */
47+
clearTransitions: () => void;
48+
/** Human-readable error message, if any */
49+
error: string | null;
50+
}
51+
52+
const MAX_TRANSITIONS = 50;
53+
54+
// ─── Hook ─────────────────────────────────────────────────────────────────────
55+
56+
/**
57+
* @param regions Array of geofence regions to monitor.
58+
* Changing this array after mount does NOT restart monitoring
59+
* automatically — call stop() then start() if regions change.
60+
*/
61+
export function useGeofencing(regions: GeofenceRegion[]): UseGeofencingReturn {
62+
const [hookState, setHookState] = useState<GeofencingHookState>("idle");
63+
const [serviceStatus, setServiceStatus] = useState<GeofencingStatus | null>(null);
64+
const [transitions, setTransitions] = useState<GeofenceTransition[]>([]);
65+
const [error, setError] = useState<string | null>(null);
66+
67+
// Keep a stable ref to regions so the transition callback doesn't close over
68+
// a stale value
69+
const regionsRef = useRef(regions);
70+
useEffect(() => { regionsRef.current = regions; }, [regions]);
71+
72+
// ── Transition handler ──────────────────────────────────────────────────────
73+
74+
const handleTransition = useCallback((t: GeofenceTransition) => {
75+
setTransitions((prev) => [t, ...prev].slice(0, MAX_TRANSITIONS));
76+
}, []);
77+
78+
// ── Start ───────────────────────────────────────────────────────────────────
79+
80+
const start = useCallback(async () => {
81+
setHookState("requesting_permissions");
82+
setError(null);
83+
84+
const status = await startGeofencing(regionsRef.current, handleTransition);
85+
setServiceStatus(status);
86+
87+
if (status.active) {
88+
setHookState("active");
89+
} else {
90+
setHookState("error");
91+
setError(status.reason ?? "Failed to start geofencing");
92+
}
93+
}, [handleTransition]);
94+
95+
// ── Stop ────────────────────────────────────────────────────────────────────
96+
97+
const stop = useCallback(async () => {
98+
await stopGeofencing();
99+
setHookState("stopped");
100+
const status = await getGeofencingStatus();
101+
setServiceStatus(status);
102+
}, []);
103+
104+
// ── Clear transitions ───────────────────────────────────────────────────────
105+
106+
const clearTransitions = useCallback(() => {
107+
setTransitions([]);
108+
}, []);
109+
110+
// ── Sync status on mount ────────────────────────────────────────────────────
111+
112+
useEffect(() => {
113+
let cancelled = false;
114+
115+
getGeofencingStatus().then((status) => {
116+
if (cancelled) return;
117+
setServiceStatus(status);
118+
if (status.active) setHookState("active");
119+
});
120+
121+
return () => { cancelled = true; };
122+
}, []);
123+
124+
// ── Cleanup on unmount ──────────────────────────────────────────────────────
125+
126+
useEffect(() => {
127+
return () => {
128+
// Do not stop the background task on unmount — it should keep running
129+
// even when the component is unmounted. Call stop() explicitly to halt.
130+
};
131+
}, []);
132+
133+
return {
134+
hookState,
135+
serviceStatus,
136+
transitions,
137+
latestTransition: transitions[0] ?? null,
138+
start,
139+
stop,
140+
clearTransitions,
141+
error,
142+
};
143+
}

0 commit comments

Comments
 (0)