{"_id":"@andynursa/checkpoint-react-native","name":"@andynursa/checkpoint-react-native","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@andynursa/checkpoint-react-native","version":"0.1.0","description":"Checkpoint location / geofence / ingest SDK for React Native (iOS + Android). A thin bridge over the @andynursa/checkpoint-capacitor native cores; all detection logic runs server-side.","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","react-native":"src/index.ts","source":"src/index.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","typecheck:test":"tsc -p tsconfig.test.json","clean":"rm -rf dist"},"keywords":["react-native","geofence","location","background-location","checkpoint","turbomodule"],"license":"UNLICENSED","repository":{"type":"git","url":"git+https://github.com/nursa-com/checkpoint-irl.git"},"dependencies":{"@andynursa/checkpoint-capacitor":"^0.1.0"},"peerDependencies":{"@andynursa/checkpoint-capacitor":"^0.1.0","react":"*","react-native":">=0.71.0"},"devDependencies":{"react-native":"0.76.0","typescript":"^5.8.3"},"codegenConfig":{"name":"CheckpointGeofenceSpec","type":"modules","jsSrcsDir":"src","android":{"javaPackageName":"com.checkpoint.reactnative"}},"engines":{"node":">=18"},"_id":"@andynursa/checkpoint-react-native@0.1.0","bugs":{"url":"https://github.com/nursa-com/checkpoint-irl/issues"},"homepage":"https://github.com/nursa-com/checkpoint-irl#readme","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-Pr3/+WLrCGOIXrd888GzJLDh3EOTbYh7ylttCcmp/0v/K/aQzfZScWzO8vZRJpiK5U4RgnEkABaWw4N2O3hxgQ==","shasum":"316c60760efb3f262a5e8d41c70ce7b30590d6af","tarball":"https://registry.npmjs.org/@andynursa/checkpoint-react-native/-/checkpoint-react-native-0.1.0.tgz","fileCount":23,"unpackedSize":80772,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDCxIkdJ/rFsQvSzRgJ7SaiAsNb7Syzmr8+3s/59d5X1AIhAIpaSlxsYrynHBLEG4Maeetf4shA9zNDphS2zqc6S7+X"}]},"_npmUser":{"name":"andynursa","email":"jake.anderson@nursa.com"},"directories":{},"maintainers":[{"name":"andynursa","email":"jake.anderson@nursa.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/checkpoint-react-native_0.1.0_1782364889520_0.8671724084382264"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-25T05:21:29.346Z","0.1.0":"2026-06-25T05:21:29.648Z","modified":"2026-06-25T05:21:29.847Z"},"maintainers":[{"name":"andynursa","email":"jake.anderson@nursa.com"}],"description":"Checkpoint location / geofence / ingest SDK for React Native (iOS + Android). A thin bridge over the @andynursa/checkpoint-capacitor native cores; all detection logic runs server-side.","homepage":"https://github.com/nursa-com/checkpoint-irl#readme","keywords":["react-native","geofence","location","background-location","checkpoint","turbomodule"],"repository":{"type":"git","url":"git+https://github.com/nursa-com/checkpoint-irl.git"},"bugs":{"url":"https://github.com/nursa-com/checkpoint-irl/issues"},"license":"UNLICENSED","readme":"# @checkpoint/react-native\n\nA **thin** React Native bridge over the two device-verified Checkpoint native\ncores (`@checkpoint/capacitor`'s iOS `GeofenceManager` + Android Play Services /\nforeground-service layer). It re-implements **no** geofencing logic — that's\nserver-side. The wrapper's only jobs are (a) expose the native API in JS/TS and\n(b) wire the platform permissions / background modes / manifest entries the cores\nrequire. This mirrors how HyperTrack ships its RN SDK over its native cores.\n\n```\nJS  →  Checkpoint.init({ publishableKey })\n       NativeGeofence.configure({ … }); NativeGeofence.addFence({ … })\n        │\n        ▼  (native module — same name \"CheckpointGeofence\" on both platforms)\niOS  GeofenceManager.shared   ── or ──  Android GeofenceStore + Play Services\n        │                                          │\n        ▼                                          ▼\n   region wake → POST /v1/ingest from URLSession / OkHttp  (NEVER a JS fetch)\n```\n\n> **Status — not published.** This package and the native core it binds to\n> (`@checkpoint/capacitor`) both live on unmerged branches. The npm names and the\n> native dependency coordinates below are **intended** names. See\n> [Dependency on the native core](#dependency-on-the-native-core).\n\n## Architecture: classic bridge, hosted on New Arch\n\nBoth native modules are **classic** bridge modules (iOS `RCTEventEmitter`, Android\n`ReactContextBaseJavaModule`) rather than Swift/Kotlin TurboModules.\n\n- A TurboModule codegen spec (`src/NativeCheckpointGeofence.ts`) IS shipped, so the\n  JS resolves the module through `TurboModuleRegistry` on the New Architecture and\n  through `NativeModules` on the classic one — same registry name\n  (`\"CheckpointGeofence\"`), one resolution path.\n- The **native implementations** are classic-bridge because RN's interop layer\n  hosts a legacy module unchanged under bridgeless mode, so one Swift file + one\n  Java file serve both architectures. Every method is a trivial forward to the\n  engine; there is no synchronous per-frame hot path that would justify the extra\n  Objective-C++ / JNI codegen shim a fully-native TurboModule needs.\n\n## Install (intended)\n\n```sh\nyarn add @checkpoint/react-native\ncd ios && pod install\n```\n\nAutolinking (`react-native.config.js` + the RN gradle/CocoaPods plugins) wires the\niOS pod and the Android package — no `MainApplication` or `Podfile` edits.\n\n## Usage (mirrors the universal SDK contract)\n\n```ts\nimport { Checkpoint, NativeGeofence } from \"@checkpoint/react-native\";\n\n// 1. Bootstrap the transport. baseUrl + anonKey are REQUIRED (no baked defaults —\n//    a published SDK must not ship a platform ref). publishableKey is safe in a binary.\nCheckpoint.init({\n  publishableKey: \"pk_live_…\",\n  baseUrl: \"https://<project>.supabase.co\",\n  anonKey: \"<anon>\",\n});\n\n// 2. Persist creds + subject natively so a BACKGROUND relaunch can POST without JS.\nawait NativeGeofence.configure({\n  baseUrl: \"https://<project>.supabase.co\",\n  anonKey: \"<anon>\",\n  publishableKey: \"pk_live_…\",\n  subjectExternalId: \"nurse-123\",   // YOUR id for the subject\n  trackingMode: \"geofence\",          // geofence (default) | always | off\n});\n\n// 3. Permissions (see the platform ladder below).\nawait NativeGeofence.requestAlwaysAuthorization();\nawait NativeGeofence.requestNotificationAuthorization();\nawait NativeGeofence.requestBatteryExemption(); // Android only; no-op on iOS\n\n// 4. Register the perimeter ring → native OS geofence (wakes a force-quit app).\nawait NativeGeofence.addFence({\n  id: \"facility-1\", latitude: 40.0, longitude: -111.0, radius: 200,\n});\n\n// 5. React to crossings while JS is alive (the native layer already POSTed the wake\n//    ping regardless). Detection — arrivals/exits/dwell — is server-side.\nconst sub = await NativeGeofence.addListener(\"regionEvent\", (e) => {\n  console.log(e.type, e.regionId, e.latitude, e.longitude, e.timestamp);\n});\n// later: sub.remove();\n\n// Tracking mode via the ergonomic facade (identical to @checkpoint/capacitor):\nawait Checkpoint.setTrackingMode(\"always\");\nconst { mode, streaming } = await Checkpoint.getTrackingMode();\n```\n\nThe public surface (`Checkpoint`, `NativeGeofence`, `TrackingMode`, `RegionEvent`,\n`NativeDiagnostics`) is **byte-identical** to `@checkpoint/capacitor` — that\ncross-wrapper uniformity is the whole point.\n\n### Cross-wrapper listener idiom\n\nThe **types and wire values** are uniform across all four wrappers; the **call\nsyntax** for subscribing to region events is idiomatic per platform (this is the\none place the \"uniform API\" claim is scoped to types, not literal call syntax):\n\n| Wrapper | Subscribe | Unsubscribe |\n|---|---|---|\n| Capacitor | `addListener('regionEvent', cb)` → `Promise<handle>` | `handle.remove()` |\n| **React Native** | `addListener('regionEvent', cb)` → `Promise<{ remove }>` | `sub.remove()` |\n| Expo | `addListener('regionEvent', cb)` (re-exports RN) | `sub.remove()` |\n| Flutter | `addRegionEventListener(cb)` → `CheckpointListenerHandle` | `await handle.remove()` |\n| .NET MAUI | `RegionEvent += handler;` (C# `event`) | `RegionEvent -= handler;` |\n\nThe event name (`\"regionEvent\"`), payload (`RegionEvent`), and `TrackingMode` wire\nvalues are identical everywhere. The shared conformance fixture\n(`test/conformance.spec.ts`, mirrored in each wrapper) asserts that.\n\n## Platform configuration\n\n### iOS — `Info.plist`\n\n```xml\n<key>NSLocationWhenInUseUsageDescription</key>\n<string>We use your location to confirm arrival at your shift facility.</string>\n<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>\n<string>Background location lets us record arrival/departure even when the app is closed.</string>\n<key>UIBackgroundModes</key>\n<array><string>location</string></array>\n```\n\n### Android — permission ladder (driven from JS via `PermissionsAndroid`)\n\nThe library manifest declares the permissions + the core's receivers/services\n(folded in by manifest-merge from the core AAR). The **runtime** grant order is\nyour app's responsibility and must be staged — Android 10+ forbids bundling\nbackground with foreground:\n\n1. `ACCESS_FINE_LOCATION` (foreground) — `PermissionsAndroid.request(...)`.\n2. **Then** `ACCESS_BACKGROUND_LOCATION` (\"Allow all the time\") — a SEPARATE\n   request after foreground is granted. A geofence armed foreground-only never\n   wakes a killed app.\n3. `POST_NOTIFICATIONS` (API 33+).\n4. Call `NativeGeofence.requestAlwaysAuthorization()` after the background grant so\n   the module re-registers fences for the killed-app path.\n5. `NativeGeofence.requestBatteryExemption()` on aggressive OEMs (Samsung One UI\n   especially) — an optimized app is force-stopped and its receivers disabled.\n\nSee the Checkpoint docs: `docs/guides/whitelisting.md`,\n`docs/guides/store-submission.md`, `docs/guides/mock-locations.md`.\n\n## Dependency on the native core\n\nThis wrapper binds to the **same** native cores as `@checkpoint/capacitor`:\n\n- **iOS** — `ios/CheckpointGeofence.swift` does `import CheckpointCapacitor` and\n  drives `GeofenceManager.shared`. Intended pod dependency: `CheckpointCapacitor`.\n- **Android** — `CheckpointGeofenceModule.java` calls\n  `com.checkpoint.capacitor.GeofenceStore` / `ContinuousLocationService` /\n  `GeofencingClient`. Intended Maven dependency:\n  `com.checkpoint:checkpoint-android-core`.\n\nNeither coordinate is published yet. The core lives in `@checkpoint/capacitor` on\nthe unmerged but **device-verified** branch `feat/sdk-extraction-capacitor` (an\niOS Pod + an Android Capacitor module project, not yet standalone artifacts). Until\nthey're published, an integrator vendors the core via local path:\n\n```ruby\n# ios/Podfile\npod 'CheckpointCapacitor', :path => '../node_modules/@checkpoint/capacitor'\n```\n\n```gradle\n// settings.gradle — include the core module\ninclude ':checkpoint-capacitor'\nproject(':checkpoint-capacitor').projectDir =\n    new File('../node_modules/@checkpoint/capacitor/android')\n```\n\n### Core access-control prerequisite (a real, tracked gap)\n\nBinding from a separate module requires two **mechanical, no-logic** changes to the\ncore (they do not alter behavior):\n\n- **iOS**: `GeofenceManager`'s engine methods (`configure`, `addFence`,\n  `clearFences`, `requestAlwaysAuthorization`, `requestNotificationAuthorization`,\n  `applyTrackingMode`, `currentMode`, `isStreaming`, `monitoredCount`,\n  `diagnostics`, `authorizationStatusString`, the `onRegionEvent` callback) and the\n  `TrackingMode` enum are currently `internal`. They must be widened to `public`\n  (only `shared` + `reviveForBackgroundLaunch` are public today).\n- **Android**: `GeofenceStore`'s methods and the services' `start`/`stop`/`RUNNING`\n  are package-private (`com.checkpoint.capacitor`). They must be widened to `public`\n  **or** this module must move into that package.\n\n## Known gaps / uncertainties\n\n- **Android `regionEvent` is native-only in the current core.** The Android\n  broadcast receiver POSTs + fires a notification but does **not** emit a JS event;\n  the Capacitor core has no `notifyListeners`/`RCTDeviceEventEmitter` on that path.\n  So `addListener(\"regionEvent\")` is wired for contract symmetry but only fires on\n  Android once the core adds a JS-bridge broadcast. iOS emits via the engine's\n  `onRegionEvent` and is live. (This is a faithful mirror of the core's current\n  behavior, not a wrapper bug.)\n- **The device REST hot-path is re-exported, not duplicated.** `mintDeviceToken`,\n  `getTrackingDirective`, and `setDeviceTrackingMode` are framework-agnostic pure\n  TypeScript in `@checkpoint/capacitor`'s `src/api.ts`. This wrapper **re-exports the\n  real implementation** (`export { … } from \"@checkpoint/capacitor\"`) rather than\n  forking it, so the wire contract (3-key directive RPC body, anon-key bearer on the\n  RPCs, publishable-key bearer on mint) is byte-identical to the core. `Checkpoint.init`\n  delegates to the core's transport so those re-exports read the same creds.\n  `@checkpoint/capacitor` is an unpublished peer (branch\n  `feat/sdk-extraction-capacitor`); `src/checkpoint-capacitor-shim.d.ts` is an ambient\n  declaration so this package typechecks standalone, and it resolves to the real module\n  once the core is published. (Like the Expo wrapper's `react-native` shim, this ambient\n  `declare module` is a pre-publish stand-in only.)\n- **Not device-verified.** Only TypeScript typechecks here. The native cores are\n  device-verified; this thin bridge over them is not yet — see the device checklist.\n- **Expo:** use a development build (config plugin TBD — the Expo wrapper is a\n  separate package). This will not run in Expo Go.\n","readmeFilename":"README.md","_rev":"1-59d41a30d0647d94b225e305b83cd795"}