{"_id":"@andynursa/checkpoint-expo","name":"@andynursa/checkpoint-expo","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@andynursa/checkpoint-expo","version":"0.1.0","description":"Checkpoint location / geofence / ingest SDK for Expo (SDK 51+). A config plugin that wires the iOS/Android background-location config plus a thin re-export of @andynursa/checkpoint-react-native — the JS API is identical. The SDK is a sensor; all detection","type":"module","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./app.plugin.js":"./app.plugin.js"},"scripts":{"build":"tsc -p tsconfig.json && tsc -p plugin/tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit && tsc -p plugin/tsconfig.json --noEmit","typecheck:test":"tsc -p tsconfig.test.json","clean":"rm -rf dist plugin/build"},"keywords":["expo","expo-config-plugin","react-native","geofence","location","background-location","checkpoint"],"license":"UNLICENSED","peerDependencies":{"@andynursa/checkpoint-react-native":"^0.1.0","expo":">=51.0.0","react":"*","react-native":"*"},"dependencies":{"@expo/config-plugins":"^8.0.0 || ^9.0.0"},"devDependencies":{"@andynursa/checkpoint-react-native":"^0.1.0","@expo/config-plugins":"^9.0.0","@types/react":"^18.2.0","expo":"^52.0.0","typescript":"^5.8.3"},"engines":{"node":">=18"},"_id":"@andynursa/checkpoint-expo@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-62LeJPKU5ZxnEyQDYka68eGuHkgAEGS3L0igqdYeQ6TOlDAMACU339brX24Fg8jUv4gmpJPwfH19ElQAV2R5MQ==","shasum":"b11488478292581218be3d399847c07aa76e8932","tarball":"https://registry.npmjs.org/@andynursa/checkpoint-expo/-/checkpoint-expo-0.1.0.tgz","fileCount":11,"unpackedSize":32815,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID3A7Nru1USbZ19qNgXUFnGZkpMf7PMxUFlrdV+orC3oAiBj9EQGkvJ73rrDIcMnG+108NdDrwNSj7Wzef4Z3UaICw=="}]},"_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-expo_0.1.0_1782365251764_0.19124452178168116"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-25T05:27:31.585Z","0.1.0":"2026-06-25T05:27:31.920Z","modified":"2026-06-25T05:27:32.125Z"},"maintainers":[{"name":"andynursa","email":"jake.anderson@nursa.com"}],"description":"Checkpoint location / geofence / ingest SDK for Expo (SDK 51+). A config plugin that wires the iOS/Android background-location config plus a thin re-export of @andynursa/checkpoint-react-native — the JS API is identical. The SDK is a sensor; all detection","keywords":["expo","expo-config-plugin","react-native","geofence","location","background-location","checkpoint"],"license":"UNLICENSED","readme":"# @checkpoint/expo\n\nCheckpoint location / geofence / ingest SDK for **Expo (SDK 51+)**.\n\nThis package is intentionally **thin**. It is exactly two things:\n\n1. An **Expo config plugin** that wires the native background-location config Expo's\n   managed/prebuild flow otherwise hides (iOS `Info.plist` usage strings +\n   `UIBackgroundModes`, Android background-location permission + foreground-service\n   declarations).\n2. A **re-export of [`@checkpoint/react-native`](../checkpoint-react-native)** — so\n   the JS API under `@checkpoint/expo` is **byte-identical** to the React Native\n   wrapper. The RN wrapper is itself a thin bridge over the two native cores. There\n   is no Expo-specific JS and no duplicated geofencing logic.\n\n> **The device is a sensor.** All detection — M-of-N arrival confirmation, dwell,\n> exit hysteresis, stale reaping — runs **server-side**. The SDK registers the\n> perimeter ring as a native OS geofence (so the OS can wake a force-quit app) and\n> POSTs location fixes from the **native** networking layer. Nothing more.\n\n## Requirements\n\n- Expo **SDK 51+**, using **prebuild + a development build** (config-plugin +\n  autolinking era).\n- **Not Expo Go.** Expo Go cannot host custom native background code (region\n  monitoring, the foreground streaming service, native ingest). You need a\n  development build: `npx expo run:ios` / `npx expo run:android` or an EAS build.\n\n## How it composes the RN wrapper\n\n```\n@checkpoint/expo\n├── app.plugin.js ──► config plugin: mods Info.plist + AndroidManifest at prebuild\n└── src/index.ts  ──► export * from \"@checkpoint/react-native\"   (identical JS API)\n                          │\n                          ▼  native module, per platform\n                CheckpointCapacitor iOS Pod  ── or ──  com.checkpoint.capacitor Android module\n                          │\n                          ▼\n        POST /v1/device/token → GET /v1/sdk/fences → register regions → POST /v1/ingest\n```\n\n## Install\n\n> **Status — unpublished.** Both `@checkpoint/expo` and its peer\n> `@checkpoint/react-native` are **target names**, not yet published artifacts; the\n> RN wrapper lives on the wrapper-SDK extraction branch and binds to the\n> `@checkpoint/capacitor` native cores (also unpublished — on\n> `feat/sdk-extraction-capacitor`). Steps below are the intended shape.\n\n```sh\nnpx expo install @checkpoint/expo @checkpoint/react-native\n```\n\nAdd the config plugin to `app.json` / `app.config.js`:\n\n```json\n{\n  \"expo\": {\n    \"plugins\": [\n      [\n        \"@checkpoint/expo\",\n        {\n          \"locationWhenInUsePermission\": \"Acme records arrivals and departures at your assigned facilities.\",\n          \"locationAlwaysAndWhenInUsePermission\": \"Acme records an arrival or departure even when the app is closed.\",\n          \"isAndroidBackgroundLocationEnabled\": true,\n          \"isAndroidBatteryExemptionEnabled\": true\n        }\n      ]\n    ]\n  }\n}\n```\n\nAll props are optional — sensible store-review-safe defaults are applied. Then\ngenerate native projects and build a dev client:\n\n```sh\nnpx expo prebuild\nnpx expo run:ios      # or run:android\n```\n\n### What the plugin injects\n\n**iOS `Info.plist`**\n\n- `NSLocationWhenInUseUsageDescription`\n- `NSLocationAlwaysAndWhenInUseUsageDescription` — **required** for background\n  region wakes\n- `NSLocationAlwaysUsageDescription` (legacy)\n- `UIBackgroundModes` += `location`\n\n**Android `AndroidManifest.xml`** (host-app permissions)\n\n- `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`\n- `ACCESS_BACKGROUND_LOCATION` (API 29+)\n- `POST_NOTIFICATIONS` (API 33+)\n- `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_LOCATION`\n- `RECEIVE_BOOT_COMPLETED`\n- `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`\n\nThe receivers/services themselves (`GeofenceBroadcastReceiver`,\n`GeofencePostService`, `ContinuousLocationService`, `BootReceiver`) ship in the\n`com.checkpoint.capacitor` library manifest and are folded in by the\nmanifest-merger via autolinking — the plugin does **not** redeclare them.\n\n## Usage\n\nThe API is the universal Checkpoint contract — identical to every other wrapper:\n\n```ts\nimport { Checkpoint, NativeGeofence } from \"@checkpoint/expo\";\nimport type { RegionEvent, TrackingMode } from \"@checkpoint/expo\";\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. Configure the native layer (persists creds + subject so a background\n//    relaunch can POST without JS). deviceId is optional.\nawait NativeGeofence.configure({\n  baseUrl: \"https://<project>.supabase.co\",\n  anonKey: \"<anon>\",\n  publishableKey: \"pk_live_…\",\n  subjectExternalId: \"your-subject-id\",\n});\n\n// 3. Ask for Always authorization (required for background region wakes) and,\n//    on Android, the battery-optimization exemption + notification permission.\nawait NativeGeofence.requestAlwaysAuthorization();\nawait NativeGeofence.requestNotificationAuthorization();\nawait NativeGeofence.requestBatteryExemption(); // no-op on iOS\n\n// 4. Register the armed perimeter rings (your app pulls them from /v1/sdk/fences).\nawait NativeGeofence.addFence({\n  id: \"facility-123\",\n  latitude: 37.422,\n  longitude: -122.084,\n  radius: 200,\n  name: \"HQ\",\n});\n\n// 5. Pick a tracking mode (server policy + user choice resolve the directive).\nawait Checkpoint.setTrackingMode(\"geofence\"); // \"geofence\" | \"always\" | \"off\"\n\n// 6. Listen for crossings while the app is alive (the native layer POSTs the\n//    crossing even when JS is dead — this is best-effort UI sugar).\nconst sub = await NativeGeofence.addListener(\"regionEvent\", (e: RegionEvent) => {\n  console.log(e.type, e.regionId, e.latitude, e.longitude, e.accuracy, e.timestamp);\n});\n// later: await sub.remove();\n```\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## Before you ship\n\nBackground location is mostly an OS-power-management and store-review problem, not a\ncode problem. Read these first:\n\n- **Battery optimization & whitelisting** — Android Doze / OEM killers silently kill\n  background geofencing (`docs/guides/whitelisting.md`).\n- **Store submission** — App Store / Play **reject** background-location apps without\n  the right strings, background modes, and prominent disclosure\n  (`docs/guides/store-submission.md`).\n- **Test with mock locations** — simulate enter/exit/dwell against the server engine\n  (`docs/guides/mock-locations.md`).\n\n## License\n\nUNLICENSED — internal Nursa / Checkpoint package.\n","readmeFilename":"README.md","_rev":"1-de06a8071b22b1693dd2e495d34f7358"}