@geoseal/cordova
v0.2.1
Published
Geoseal location / geofence / ingest SDK for Apache Cordova (iOS + Android). A thin bridge over the @geoseal/capacitor native cores; all detection logic runs server-side.
Maintainers
Readme
@geoseal/cordova
Geoseal location / geofence / ingest SDK for Apache Cordova (iOS + Android).
Plugin id: checkpoint-cordova-plugin. The SDK is a sensor; all detection
logic runs server-side.
A thin bridge over the Geoseal native cores:
| Layer | iOS | Android |
| --- | --- | --- |
| Native engine | CheckpointCore pod (GeofenceManager) | dev.checkpoint:checkpoint-core AAR (GeofenceStore + receivers/services) |
| Bridge | ios/CheckpointCordova.swift (CDVPlugin) | android/CheckpointCordova.java (CordovaPlugin) |
| JS | www/checkpoint.js — generated by npm run build from src/index.ts, which single-sources the REST/presence layer from @geoseal/capacitor/core | same |
The JS module clobbers window.Checkpoint: the Checkpoint facade methods
(init / isConfigured / setTrackingMode / getTrackingMode /
getTrackingDirective / setDeviceTrackingMode) at the top level, plus every
named export (NativeGeofence, mintDeviceToken, pullArmedFences,
ingestFix, reportTrackingOutages, …) namespaced on the same object.
Requires cordova-android >= 12 and cordova-ios >= 8 (the Swift bridge
targets the Cordova 8 plugin API).
Install
cordova plugin add @geoseal/cordovaThe plugin's plugin.xml declares the native cores (<podspec> pod
CheckpointCore ~> 0.1, which resolves from the CocoaPods trunk (CDN);
<framework> dev.checkpoint:checkpoint-core:0.1.0).
iOS — no manual steps:
CheckpointCoreis live on the CocoaPods trunk, socordova platform add iosresolves it from the CDN. On a machine with a stale CDN cache, runpod install --repo-updateonce inplatforms/ios.Android —
dev.checkpoint:checkpoint-coreis not on Maven Central yet; one-time per machine, publish the core AAR into~/.m2(the plugin ships agradleReferencethat adds the content-filteredmavenLocalrepo — remove it once the coordinate is published):cd node_modules/@geoseal/capacitor/android-core && ./gradlew publishToMavenLocal(No copy of
@geoseal/capacitorhandy?npm i --no-save @geoseal/capacitorin the app first, or use a monorepo checkout:<checkout>/packages/checkpoint-capacitor/android-core.)
From-checkout install (development)
# 0. One-time per machine — build the JS bundle + the Android core into ~/.m2:
cd <checkout>/packages/checkpoint-capacitor && npm install --no-save # builds dist/ (prepare)
cd <checkout>/packages/checkpoint-cordova && npm install --no-save && npm run build
cd <checkout>/packages/checkpoint-capacitor/android-core && ./gradlew publishToMavenLocal
# 1. Add the plugin from the local path:
cordova plugin add <checkout>/packages/checkpoint-cordovaiOS still resolves CheckpointCore from the CDN; to pin the checkout's copy
instead, swap the generated platforms/ios/Podfile line for
pod 'CheckpointCore', :path => '<checkout>/packages/checkpoint-capacitor' and
re-run pod install (note: cordova plugin add/rm regenerates the Podfile, so
re-apply the :path line after plugin changes; cordova prepare/build leave
it alone).
iOS setup (app responsibilities)
Info.plist usage strings — the app owns its user-facing copy; add to
config.xml(the plugin deliberately does not overwrite these):<platform name="ios"> <edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription"> <string>Used to confirm on-site presence during a shift.</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="NSLocationAlwaysAndWhenInUseUsageDescription"> <string>Background location powers automatic arrival/departure detection.</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="UIBackgroundModes"> <array><string>location</string></array> </edit-config> </platform>(
NSLocationAlwaysUsageDescriptionmay be added too for older tooling; iOS 11+ reads the AndWhenInUse key.)Cold-relaunch revive (R2) — when iOS relaunches the app in the background for a location event, revive the engine without waiting for JS. In the app's
AppDelegate.swift(or a small plugin/hook of your own):import CheckpointCore func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if launchOptions?[.location] != nil { GeofenceManager.shared.reviveForBackgroundLaunch() } return true }The plugin's deployment-target preference (14.0) and
SwiftVersionare injected intoconfig.xmlautomatically.
Android setup
Nothing manual: the engine AAR's manifest already carries the permissions
(fine/coarse/background location, notifications, foreground-service location,
boot) and the engine's receivers/services; the manifest merger brings them into
the app. The runtime prompts are driven through the plugin
(requestAlwaysAuthorization two-step foreground → "Allow all the time"
escalation with fence re-registration on grant, requestBatteryExemption for
Doze/OEM app-standby).
Usage
document.addEventListener("deviceready", async () => {
// Layer 2 — REST transport (inert if any cred is missing; check isConfigured()).
Checkpoint.init({
baseUrl: "https://<project-ref>.supabase.co",
anonKey: "<anon key>",
publishableKey: "pk_…",
});
if (!Checkpoint.isConfigured()) return;
// Layer 1 — native engine (persists creds for background relaunch POSTs).
await Checkpoint.NativeGeofence.configure({
baseUrl: "https://<project-ref>.supabase.co",
anonKey: "<anon key>",
publishableKey: "pk_…",
subjectExternalId: "nurse-123",
deviceId: "cordova-nurse-123",
// Optional server directive fields: trackingMode / streamNow / minIntervalS
// / maxStrayStreamS (stray-stream cap, default 600 s).
});
await Checkpoint.NativeGeofence.requestAlwaysAuthorization();
await Checkpoint.NativeGeofence.requestNotificationAuthorization();
await Checkpoint.NativeGeofence.requestBatteryExemption(); // Android; iOS no-op
// Arm fences from the platform:
const token = await Checkpoint.mintDeviceToken({ externalId: "nurse-123", deviceRef: "cordova-nurse-123" });
const pull = await Checkpoint.pullArmedFences(token.subject_id);
for (const fence of pull.regions) {
await Checkpoint.NativeGeofence.addFence({
id: fence.geofence_ref ?? fence.place_id,
latitude: fence.center.latitude,
longitude: fence.center.longitude,
radius: fence.radius_m,
name: fence.name ?? undefined,
});
}
// Live crossings while the webview is alive (background POSTs are native):
await Checkpoint.NativeGeofence.addListener("regionEvent", (e) => {
console.log(e.type, e.regionId, e.latitude, e.longitude); // 'enter' | 'exit' | 'update' | 'stray_stream_stopped'
});
});Contract
The wire contract is FROZEN and mirrored from @geoseal/capacitor
(src/definitions.ts): TrackingMode = 'geofence' | 'always' | 'off', the
RegionEvent shape (4-value type union; stray_stream_stopped carries
regionId: "" + the last off-site fix), the NativeDiagnostics field set, the
directive RPC body (always all three p_ keys), and the /v1/ingest body
shape. test/conformance.spec.ts pins it — verified by npm run
typecheck:test (a drift is a compile error, since the types come from
@geoseal/capacitor/core itself).
Developing
src/is the TypeScript source;www/checkpoint.jsis a committed build artifact (Cordova installs plugins as plain files — no npm lifecycle). After editingsrc/, runnpm run build(typecheck + fixture typecheck + esbuild bundle) and commit the regenerated bundle.- The geofence-only entry point of the reference SDK
(
@geoseal/capacitor/geofence) has no Cordova analog: Cordova js-modules are single-file clobbers, not subpath exports. Privacy-first dtok_-only embeds should simply not call the streaming/self-serve helpers.
License
Apache-2.0 (matches the monorepo and the native cores).
