@hcl-cdp-ta/geofence-sdk
v3.0.0
Published
Geofencing SDK for web-based geolocation tracking
Maintainers
Readme
@hcl-cdp-ta/geofence-sdk
A browser-based geofencing SDK for detecting when users enter or exit defined geographic areas.
Features
- Real-time geolocation tracking with configurable polling intervals
- Event-driven architecture for geofence entry/exit detection
- TypeScript support with full type definitions
- Lightweight with no external dependencies
- Works with any geofence management backend
Installation
npm install @hcl-cdp-ta/geofence-sdkQuick Start
import { GeofenceMonitor } from "@hcl-cdp-ta/geofence-sdk"
// Initialize the monitor
const monitor = new GeofenceMonitor({
apiUrl: "https://your-geofence-api.com",
pollingInterval: 5000, // Check location every 5 seconds
enableHighAccuracy: true,
debug: true, // Enable console logging
})
// Listen for geofence events
monitor.on("enter", geofence => {
console.log(`Entered geofence: ${geofence.name}`)
// Send event to your analytics platform (e.g., HCL CDP)
sendToAnalytics({
event: "geofence_enter",
geofenceId: geofence.id,
geofenceName: geofence.name,
})
})
monitor.on("exit", geofence => {
console.log(`Exited geofence: ${geofence.name}`)
sendToAnalytics({
event: "geofence_exit",
geofenceId: geofence.id,
geofenceName: geofence.name,
})
})
monitor.on("error", error => {
console.error("Geofence monitoring error:", error)
})
monitor.on("position", position => {
console.log("Current position:", position.coords.latitude, position.coords.longitude)
})
// Start monitoring
await monitor.start()
// Later: stop monitoring
monitor.stop()API Reference
GeofenceMonitor
The main class for monitoring geofences.
Constructor
new GeofenceMonitor(options: GeofenceMonitorOptions)Options:
| Option | Type | Default | Description |
| ------------------------------ | --------- | -------------- | -------------------------------------------------------------- |
| apiUrl | string | Required | Base URL of your geofence API |
| appId | string | "default-app"| Application identifier for multi-app support |
| pollingInterval | number | 10000 | How often to check location (milliseconds) |
| enableHighAccuracy | boolean | true | Use GPS for high accuracy positioning |
| debug | boolean | false | Enable debug logging to console |
| testMode | boolean | false | Enable manual position control for testing |
| userId | string | - | User identifier (required for server-side evaluation) |
| enableServerEvaluation | boolean | false | Enable server-side geofence evaluation |
| significantMovementThreshold | number | 50 | Only report position when moved this many meters (server mode) |
Methods
start(): Promise<void>
Start monitoring geofences. Fetches geofences from the API and begins tracking user location.
await monitor.start()stop(): void
Stop monitoring geofences and clear all intervals.
monitor.stop()getStatus(): MonitorStatus
Get the current monitoring status.
const status = monitor.getStatus()
console.log("Is running:", status.isRunning)
console.log("Current geofences:", status.currentGeofences)
console.log("Last position:", status.lastPosition)Returns:
{
isRunning: boolean;
currentGeofences: Geofence[];
lastPosition: GeolocationPosition | null;
}checkPosition(): Promise<void>
Manually trigger a position check (useful for testing).
await monitor.checkPosition()on(event: GeofenceEvent, listener: Function): void
Add an event listener.
Events:
'enter'- Fired when entering a geofence. ReceivesGeofenceobject.'exit'- Fired when exiting a geofence. ReceivesGeofenceobject.'position'- Fired on each position update. ReceivesGeolocationPositionobject.'error'- Fired on errors. ReceivesErrorobject.
monitor.on("enter", (geofence: Geofence) => {
// Handle entry
})off(event: GeofenceEvent, listener: Function): void
Remove an event listener.
const handleEnter = geofence => {
/* ... */
}
monitor.on("enter", handleEnter)
monitor.off("enter", handleEnter)Types
Geofence
interface Geofence {
id: string
name: string
latitude: number
longitude: number
radius: number // in meters
}MonitorStatus
interface MonitorStatus {
isRunning: boolean
currentGeofences: Geofence[]
lastPosition: GeolocationPosition | null
}Evaluation Modes
The SDK supports two evaluation modes:
Client-Side Evaluation (Default)
The SDK fetches geofences from the server and evaluates them locally in the browser. This is the simplest mode and suitable for most use cases.
How it works:
- SDK fetches all enabled geofences once at startup
- Position is polled from browser's Geolocation API
- Distance calculations performed locally using Haversine formula
- Enter/exit events fired immediately when transitions detected
- No network requests during monitoring (only initial geofence fetch)
Example:
const monitor = new GeofenceMonitor({
apiUrl: "https://api.example.com",
pollingInterval: 10000,
enableHighAccuracy: true,
debug: false,
})
monitor.on("enter", geofence => {
// Event fired client-side
console.log("Entered:", geofence.name)
})
await monitor.start()Server-Side Evaluation (Server-Authoritative)
The SDK sends position updates to the server, which evaluates geofences and returns events. This mode enables server-side integrations like webhooks and CDP event routing.
How it works:
- Position is polled from browser's Geolocation API
- Position sent to server only when moved > threshold meters (default: 50m)
- Server evaluates geofences and maintains user state
- Server dispatches events to configured adapters (CDP, webhooks, database)
- Server returns events in response, which SDK emits locally
Example:
const monitor = new GeofenceMonitor({
apiUrl: "https://api.example.com",
appId: "my-mobile-app", // Optional: defaults to "default-app"
userId: "user-123", // Required for server mode
enableServerEvaluation: true, // Enable server-authoritative mode
significantMovementThreshold: 50, // Only report when moved >50m (default)
pollingInterval: 10000,
enableHighAccuracy: true,
debug: false,
})
monitor.on("enter", geofence => {
// Event came from server evaluation
// Server has already fired adapters (CDP, webhooks, etc.)
console.log("Entered:", geofence.name)
})
monitor.on("exit", geofence => {
console.log("Exited:", geofence.name)
})
await monitor.start()Server-Side Benefits:
- Events appear as "HTTP API" source in CDP/analytics tools (not "JavaScript/Web")
- Server can route events to multiple destinations via adapters (webhooks, CDP, email, etc.)
- Centralized event logging and audit trail
- Reduced network traffic via movement threshold
- Server is source of truth for geofence state
- Multi-app support: Different apps can have same user IDs without conflicts
Requirements for Server Mode:
- Backend must implement
POST /api/events/positionendpoint userIdmust be provided in SDK configuration- Server must maintain user geofence state in database using composite key
(appId, userId)
API Endpoint Requirements:
The server must implement the following endpoint:
POST {apiUrl}/api/events/position
Request body:
{
"appId": "my-mobile-app",
"userId": "user-123",
"latitude": 37.7749,
"longitude": -122.4194,
"accuracy": 10,
"timestamp": 1234567890000,
"speed": 2.5,
"heading": 180
}Response:
{
"success": true,
"events": [
{
"type": "enter",
"geofence": {
"id": "abc123",
"name": "Downtown Store",
"latitude": 40.7128,
"longitude": -74.006,
"radius": 500
},
"timestamp": "2024-01-01T12:00:00.000Z"
}
]
}Multi-App Support
The SDK supports multiple applications using the same geofencing backend without user ID conflicts through the appId parameter.
Why Use App IDs?
If you have multiple applications (e.g., iOS app, Android app, web app) using the same geofencing system, users with the same ID in different apps would conflict. The appId parameter creates namespace isolation.
Example scenario:
- iOS app has user "user-123"
- Android app has user "user-123"
- Without
appId: These would be treated as the same user - With
appId: They're tracked separately as ("ios-app", "user-123") and ("android-app", "user-123")
Usage
Simply provide an appId when initializing the SDK:
// iOS app
const monitor = new GeofenceMonitor({
apiUrl: "https://api.example.com",
appId: "ios-app", // Identifies this as the iOS app
userId: "user-123",
enableServerEvaluation: true,
})
// Android app
const monitor = new GeofenceMonitor({
apiUrl: "https://api.example.com",
appId: "android-app", // Identifies this as the Android app
userId: "user-123", // Same user ID, different app - no conflict!
enableServerEvaluation: true,
})Default Behavior
If you don't specify an appId, it defaults to "default-app". This is fine for single-app deployments or backward compatibility.
const monitor = new GeofenceMonitor({
apiUrl: "https://api.example.com",
// appId defaults to "default-app"
})Server-Side State Management
When using server-side evaluation, the backend maintains user state using a composite key of (appId, userId). This ensures:
- Each app's users are tracked independently
- Geofence enter/exit events are scoped per app
- Analytics events include
app_idfor segmentation - No user ID collisions across apps
Usage Examples
React Integration
import { useEffect, useState } from "react"
import { GeofenceMonitor, Geofence } from "@geofence/sdk"
function GeofenceTracker() {
const [currentGeofences, setCurrentGeofences] = useState<Geofence[]>([])
const [monitor] = useState(
() =>
new GeofenceMonitor({
apiUrl: "https://api.example.com",
pollingInterval: 5000,
}),
)
useEffect(() => {
monitor.on("enter", geofence => {
console.log("Entered:", geofence.name)
setCurrentGeofences(prev => [...prev, geofence])
})
monitor.on("exit", geofence => {
console.log("Exited:", geofence.name)
setCurrentGeofences(prev => prev.filter(g => g.id !== geofence.id))
})
monitor.on("error", error => {
console.error("Error:", error)
})
monitor.start()
return () => {
monitor.stop()
}
}, [monitor])
return (
<div>
<h2>Current Geofences</h2>
{currentGeofences.length === 0 ? (
<p>Not in any geofence</p>
) : (
<ul>
{currentGeofences.map(g => (
<li key={g.id}>{g.name}</li>
))}
</ul>
)}
</div>
)
}Next.js Integration
"use client"
import { useEffect } from "react"
import { GeofenceMonitor } from "@geofence/sdk"
export default function GeofencePage() {
useEffect(() => {
const monitor = new GeofenceMonitor({
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
pollingInterval: 5000,
debug: process.env.NODE_ENV === "development",
})
monitor.on("enter", geofence => {
console.log("Entered:", geofence.name)
})
monitor.on("exit", geofence => {
console.log("Exited:", geofence.name)
})
monitor.start()
return () => monitor.stop()
}, [])
return <div>Geofence tracking active</div>
}Browser Compatibility
The SDK requires browser support for:
- Geolocation API
- Fetch API
- ES6 features (Map, Set, Promise)
Supported browsers:
- Chrome 50+
- Firefox 52+
- Safari 10+
- Edge 14+
Performance Considerations
Battery Life
Frequent geolocation polling can drain battery. Consider:
- Use longer polling intervals (10-30 seconds) for better battery life
- Set
enableHighAccuracy: falsewhen precision isn't critical - Stop monitoring when the app is backgrounded
Accuracy
- GPS accuracy is typically 5-50 meters
- Use geofence radii of at least 100 meters for reliable detection
- Higher polling intervals may miss brief entries into small geofences
Network Usage
- Geofences are fetched once on
start() - Each location check is local (no network requests)
- Consider refetching geofences periodically if they change frequently
API Endpoint Requirements
The SDK expects a public API endpoint at {apiUrl}/api/public/geofences that returns:
{
"geofences": [
{
"id": "abc123",
"name": "Downtown Store",
"latitude": 40.7128,
"longitude": -74.006,
"radius": 500
}
]
}The endpoint should:
- Accept GET requests
- Return JSON with a
geofencesarray - Support CORS for browser requests
- Only return enabled/active geofences
License
MIT
Support
For issues and questions, please open an issue on GitHub.
