npm package: @oraculi/telematics-sdk
Description: Telematics SDK for React Native
Latest version: 0.1.3

# @oraculi/telematics-sdk

Telematics SDK for React Native (Nitro Modules).

## Installation
```sh
npm install @oraculi/telematics-sdk react-native-nitro-modules
```

```sh
cd ios && pod install
```

Autolinking is enabled by default with the React Native CLI. If autolinking is disabled, follow
the React Native manual linking guide and point iOS to `TelematicsSdk.podspec` and Android to
`node_modules/@oraculi/telematics-sdk/android`.

## Permissions

iOS (Info.plist):
- `NSLocationWhenInUseUsageDescription`
- `NSLocationAlwaysAndWhenInUseUsageDescription`
- `NSMotionUsageDescription`
- `UIBackgroundModes` with `location` (required for background tracking)
  - Trip notifications request notification permission at runtime when enabled.

Android (AndroidManifest.xml):
- `android.permission.ACCESS_FINE_LOCATION`
- `android.permission.ACCESS_COARSE_LOCATION`
- `android.permission.ACCESS_BACKGROUND_LOCATION` (Android 10+, requested by the SDK)
- `android.permission.POST_NOTIFICATIONS` (Android 13+ for trip notifications)
- `android.permission.FOREGROUND_SERVICE`
- `android.permission.FOREGROUND_SERVICE_LOCATION`

Note: The SDK always requests background location permission to enable background tracking.

## Usage
```ts
import {
  configure,
  requestPermission,
  startTracking,
  stopTracking,
  addListener,
  removeListener,
} from '@oraculi/telematics-sdk';

configure({
  apiKey: 'demo-key',
  trackingMode: 'driving',
  syncIntervalMinutes: 15,
});

await requestPermission();
startTracking();

const subId = addListener('scoreUpdated', ({ score }) => {
  console.log('Score', score?.safetyScore);
});

// Later
removeListener(subId);
stopTracking();
```

## Presets
You can pass a preset string to apply a preconfigured setup. Any explicit fields you pass override
the preset defaults. Use `preset: 'none'` to skip preset defaults entirely. If you omit `preset`,
the SDK defaults to `none` (fully customizable).

```ts
configure({
  preset: 'ride_hailing',
  apiKey: 'demo-key',
});
```

Available presets:
- `ride_hailing` (high accuracy, always-on tracking)
- `delivery` (balanced accuracy, always-on tracking)
- `car_rental` (driving-only tracking, lower sampling)
- `insurance` (high accuracy, driving-only tracking)

## Initialization
The SDK initializes itself by calling a native-only init endpoint using the `apiKey`. Initialization
is asynchronous, and tracking won't start until the init endpoint returns a 2xx response. If init
fails, `startTracking()` is a no-op and `getTrackingState().lastError` is populated.

The SDK posts to:
- `https://backoffice.speezydev.com/api/api-keys/verify` (POST)

Headers:
- `Content-Type: application/json`
- `x-api-key: <apiKey>`

## Sync (trip uploads)
Trips are submitted to:
- `https://backoffice.speezydev.com/api/trips` (POST)

Headers:
- `Content-Type: application/json`
- `x-api-key: <apiKey>`

Payload shape (top-level):
- `externalId`: trip id
- `startedAt`: ISO string
- `endedAt`: ISO string (optional)
- `distanceKm`: number
- `payload`: full telematics payload (trip/events/samples)

## Events
Supported events:
- `tripStarted`
- `tripEnded`
- `scoreUpdated`
- `syncState`
- `initState` (`message` is `pending`, `ready`, or `failed`)
- `error` (emitted when `lastError` changes)

Error payloads include `errorCode` (stable identifier) and `errorMessage` (human-readable). Common
codes: `not_initialized`, `permission_denied`, `init_missing_api_key`, `init_invalid_endpoint`,
`init_payload_failed`, `init_failed`, `location_error`, `context_unavailable` (Android only).

## Trip notifications
By default, the SDK posts local notifications when a trip starts, is in progress, and ends. Disable
them by setting `tripNotificationsEnabled: false` if you want to provide your own notifications.

## Example app
The example app lives in `examples/react-native` and installs the published SDK from npm.

- Update the API key in `examples/react-native/App.tsx`.
- The SDK is wired to the backoffice endpoints above; change the native endpoint constants if
  you need a different backend.
- Run the app using the steps in `examples/react-native/README.md`.

## Shared core
The shared C++ core lives in `packages/telematics-core/cpp` and is used by native targets via
CMake (Android) and CocoaPods (iOS). This keeps the core logic reusable for the future Swift SDK.

## API Reference

### TelematicsConfig
- `apiKey?: string` (required for init)
- `preset?: string` (`ride_hailing`, `delivery`, `car_rental`, `insurance`, or `none`; default: `none`)
- `trackingMode?: 'driving' | 'always' | 'manual'` (default: `driving`)
- `accuracy?: 'low' | 'balanced' | 'high'` (default: `balanced`)
- `syncIntervalMinutes?: number` (default: `15`, minimum: `15`, maximum: `1440`)
- `retentionDays?: number` (default: `30`)
- `scoringWeights?: { braking?: number; accel?: number; speeding?: number; phoneUse?: number; cornering?: number }`
  - defaults: braking `5`, accel `5`, speeding `3`, phoneUse `8`, cornering `3`
- `efficiencyWeights?: { idling?: number; routeEfficiency?: number }`
  - defaults: idling `40`, routeEfficiency `60`
- `sampleIntervalMs?: number` (default: `1000`, minimum: `250`)
- `sampleDistanceMeters?: number` (default: `5`)
- `sampleMaxPoints?: number` (default: `1800`, clamped to `0..10000`)
- `distanceFilter?: number` (meters; overrides the base native distance filter)
- `locationUpdateIntervalMs?: number` (minimum time between location updates; Android native, iOS throttled)
- `locationElasticityEnabled?: boolean` (default: `true`)
  - when `true`, tightens distance/time filters as speed increases
- `locationFilterEnabled?: boolean` (default: `false`)
  - enables C++ location smoothing + acceptance gating before trip/event detection
- `locationFilterUseKalman?: boolean` (default: `true`)
- `locationFilterMinDistanceMeters?: number` (default: `3`)
- `locationFilterMaxAccuracyMeters?: number` (default: `50`)
- `locationFilterMaxSpeedMps?: number` (default: `55`)
- `locationFilterSpeedWindowSize?: number` (default: `5`)
- `locationFilterHeadingWindowSize?: number` (default: `5`)
- `autoTripDetectionEnabled?: boolean` (default: `true`)
  - set to `false` to disable automatic trip start/end detection
- `walkingModeEnabled?: boolean` (default: `false`)
  - lowers the movement threshold for walking or mixed travel tests
- `tripNotificationsEnabled?: boolean` (default: `true`)
  - set to `false` to disable built-in trip notifications
- `manualTripControlEnabled?: boolean` (default: `false`)
  - debug-only: when `true`, `startTracking()` begins a trip and `stopTracking()` ends it

### Methods
- `configure(config: TelematicsConfig): void`
  - Non-blocking; triggers native initialization and applies config.
- `requestPermission(): Promise<PermissionStatus>`
- `checkPermission(): PermissionStatus`
- `startTracking(): void`
  - No-op if init has not succeeded or permission is denied.
- `stopTracking(): void`
- `getTrackingState(): TrackingState`
- `getTripHistory(limit?: number): TripSummary[]`
- `getCurrentTrip(): TripSummary | undefined`
- `getCurrentScore(): DrivingScore | undefined`
- `syncNow(): Promise<boolean>`
  - Returns `false` if init has not succeeded.
- `deleteLocalData(): void`
- `addListener(event: TelematicsEvent, callback): string`
- `removeListener(subscriptionId: string): void`

### Data models
- `TrackingState`: `{ isTracking, lastError?, lastPosition?, permission }`
- `LocationPoint`: `{ latitude, longitude, speedMps?, accuracyM?, timestampMs }`
- `TripSummary`: `{ tripId, startTime, endTime?, distanceMeters, durationSeconds, avgSpeedMps, maxSpeedMps, safetyScore, efficiencyScore?, events? }`
- `DrivingEvent`: `{ type, timestamp, lat, lng, speedMps?, severity? }`
- `DrivingScore`: `{ safetyScore, efficiencyScore?, factors }`
- `TelematicsEventPayload`: `{ trip?, score?, message?, errorCode?, errorMessage? }`

## Background behavior
- iOS: Significant Location Changes + stationary geofence are used for low-power wakeups. If the user
  force-quits the app, iOS will not relaunch it for location events.
- iOS requires "Always" location permission for background tracking; "When In Use" stops updates.
- Android: Foreground service keeps tracking active. If the user force-stops the app, tracking and
  uploads stop until the app is opened again.

## Troubleshooting

### Initialization fails
- Ensure `apiKey` is set in `configure()`.
- Verify the native init endpoint returns 2xx.
- Check network connectivity and TLS configuration.
- If you are still using the dummy endpoint, replace it in native code.

### Permission denied
- iOS: confirm Info.plist keys are present and permissions were granted.
- iOS: `requestPermission()` will attempt to upgrade When In Use -> Always if needed.
- Android: ensure location permissions are in the manifest and approved at runtime.
- Background location permission is required for tracking while the app is in the background.

### No trips recorded
- Make sure `startTracking()` is called after `requestPermission()` resolves.
- Confirm init has succeeded (no `lastError` and `startTracking()` is not a no-op).
- Trips require movement; verify on a device with GPS and motion.
- For walking or mixed travel tests, set `walkingModeEnabled: true`.
- For debug-only manual trips, set `manualTripControlEnabled: true` and use start/stop.

### Sync not happening
- Init must succeed before sync can run.
- `syncIntervalMinutes` must be > 0 (minimum 15).
- Call `syncNow()` to force a run and inspect the result.

### Unexpected battery drain
- Use `accuracy: 'balanced'` unless high precision is required.
- Consider longer sampling intervals and distances for low-frequency use cases.

### FAQ
Q: Can I disable presets?
A: Yes. Omit `preset` or set `preset: 'none'`.

Q: Can I call `configure()` more than once?
A: Yes. The SDK applies the latest config and re-runs initialization.

## Contributing

- [Development workflow](CONTRIBUTING.md#development-workflow)
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
- [Code of conduct](CODE_OF_CONDUCT.md)

## License

MIT

---

Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)