> ## Documentation Index
> Fetch the complete documentation index at: https://developer.neurofit.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Capacitor quickstart

> Use @neurofit/vitals-capacitor, a Capacitor 6 plugin that wraps the native iOS and Android SDKs.

`@neurofit/vitals-capacitor` is a Capacitor 6 plugin: a `CAPPlugin` on iOS and a `Plugin` on Android forward
every call to the native `NeurofitVitals` (Swift) or `com.neurofit.vitals` (Kotlin) SDK. The web implementation
is a stub whose methods reject with Capacitor's own `ExceptionCode.Unavailable` (the code string `UNAVAILABLE`),
so your web build compiles and you can feature-detect at runtime. Event names and payload keys are the same as the
React Native and Flutter wrappers.

<Steps>
  <Step title="Install the plugin">
    The plugin is delivered with your license.

    ```bash theme={null}
    npm install ./neurofit-vitals-capacitor-1.0.0.tgz
    npx cap sync
    ```

    The podspec links `NeurofitVitals.xcframework` from the plugin's `ios/Frameworks/` folder (binary license) or
    the Swift package sources (source license). The Android module depends on `com.neurofit:vitals-sdk:1.0.0`;
    make that artifact resolvable from `mavenLocal()` or your artifact repository, and set `minSdkVersion` to 28
    or higher in `variables.gradle`.
  </Step>

  <Step title="Platform setup">
    **iOS**: add `NSCameraUsageDescription` to `ios/App/App/Info.plist` (see the [iOS quickstart](/quickstart-ios)).
    Adding `NSMotionUsageDescription` as well is recommended, because the SDK reads the accelerometer during a
    reading.

    **Android**: the plugin's manifest declares `android.permission.CAMERA`, so nothing to add. See the
    [Android quickstart](/quickstart-android) for the optional `uses-feature` entries.
  </Step>

  <Step title="Activate and check the device">
    Plugin methods take a single options object and resolve to an object.

    ```typescript theme={null}
    import { Capacitor } from '@capacitor/core';
    import { NeurofitVitals } from '@neurofit/vitals-capacitor';

    export async function prepareVitals(licenseKey: string): Promise<boolean> {
      if (!Capacitor.isNativePlatform()) return false;

      await NeurofitVitals.activate({ licenseKey });   // rejects with code 'licenseInvalid' on a bad key

      const support = await NeurofitVitals.deviceSupport();
      if (!support.isSupported) {
        console.warn('Vitals not supported on this device:', support.reason);
        return false;
      }

      if ((await NeurofitVitals.hasCameraPermission()).granted) return true;
      const { granted } = await NeurofitVitals.requestCameraPermission();
      return granted;
    }
    ```

    Rejections carry `code` (one of the error codes listed under [Plugin interface](#plugin-interface)) and
    `message`. See [License keys](/license-keys).
  </Step>

  <Step title="Run a reading">
    The plugin runs one session at a time: `start()` creates it, and it ends with `completed`, `failed` or
    `stop()`. Add listeners **before** calling `start()`; the plugin does not buffer events.

    ```typescript theme={null}
    import type { PluginListenerHandle } from '@capacitor/core';
    import { NeurofitVitals } from '@neurofit/vitals-capacitor';
    import type { LivePreview, VitalsError, VitalsResult } from '@neurofit/vitals-capacitor';

    export async function runReading(ui: {
      guidance(g: string, startProgress: number): void;
      live(preview: LivePreview): void;
      notice(text: string): void;
    }): Promise<VitalsResult> {
      let resolveResult!: (result: VitalsResult) => void;
      let rejectResult!: (error: VitalsError) => void;
      const result = new Promise<VitalsResult>((resolve, reject) => {
        resolveResult = resolve;
        rejectResult = reject;
      });

      // Add listeners before start: the plugin does not buffer events.
      const handles: PluginListenerHandle[] = await Promise.all([
        NeurofitVitals.addListener('guidance', (e) => ui.guidance(e.guidance, e.startProgress)),
        NeurofitVitals.addListener('preview', (e) => ui.live(e)),   // rmssdMs from about 20 s
        NeurofitVitals.addListener('cancelled', () => ui.notice("Let's try that again.")),
        NeurofitVitals.addListener('completed', (e) => resolveResult(e.result)),
        NeurofitVitals.addListener('failed', (e) => rejectResult(e.error)),
      ]);

      try {
        await NeurofitVitals.start();   // camera + torch on, then positioning
        return await result;
      } finally {
        await Promise.all(handles.map((h) => h.remove()));
        await NeurofitVitals.stop();    // idempotent; torch off, session released
      }
    }
    ```

    `NeurofitVitals.cancelReading()` cancels the reading in progress (a `cancelled` event with reason
    `cancelledByApp`; the session returns to positioning). It is ignored while positioning. `stop()` tears the
    camera down and releases the session; it is idempotent. Once `measurementComplete` has arrived, a stopped
    session still delivers its `completed` (or `failed`) event to your listeners. `removeAllListeners()` clears
    every listener at once when your reading screen is destroyed.

    `start()` rejects with `notActivated`, `cameraPermissionDenied`, `unsupportedDevice`, on iOS also
    `cameraUnavailable`, or `internalError` while a session is already running. On Android a camera that cannot
    be opened arrives afterwards as a `failed` event with code `cameraUnavailable`.
  </Step>
</Steps>

## Foreground, orientation and screen

A reading needs the app in the foreground for the whole minute, but leaving it does not end the session.
Backgrounding, or another app taking the camera, cancels a reading in progress with a `cancelled` event whose
`reason` is `interrupted`, and the session resumes positioning by itself when the app is back. Lock the reading
screen's orientation while a session runs (`@capacitor/screen-orientation`) and keep the screen awake
(`@capacitor-community/keep-awake`).

To keep the camera positioning without starting a reading (an intro or a sheet covers the screen, say), call
`NeurofitVitals.setAutoStartEnabled({ enabled: false })`, and the same with `enabled: true` when the user is
ready. A reading already measuring is unaffected. The setting applies to the current session and to every later
`start()` until you change it.

## Plugin interface

```typescript definitions.ts theme={null}
export interface NeurofitVitalsPlugin {
  activate(options: { licenseKey: string }): Promise<void>;
  deviceSupport(): Promise<DeviceSupport>;                       // { isSupported, reason }
  hasCameraPermission(): Promise<{ granted: boolean }>;          // never prompts
  requestCameraPermission(): Promise<{ granted: boolean }>;
  start(): Promise<void>;
  stop(): Promise<void>;
  cancelReading(): Promise<void>;
  setAutoStartEnabled(options: { enabled: boolean }): Promise<void>;   // default true
  addListener<Name extends VitalsEventName>(
    eventName: Name,
    listenerFunc: (event: VitalsEventPayload<Name>) => void,
  ): Promise<PluginListenerHandle>;
  removeAllListeners(): Promise<void>;
}
```

Types (`DeviceSupport`, `LivePreview`, `VitalsResult`, `VitalsError`, the event payload interfaces and the
`VitalsEvent` union) are exported from the package and match the
[React Native shapes](/quickstart-react-native#event-and-result-shapes) exactly. Error codes are
`notActivated`, `licenseInvalid`, `cameraPermissionDenied`, `cameraUnavailable`, `unsupportedDevice`,
`noHeartRate` and `internalError`; on the web, rejections carry Capacitor's `UNAVAILABLE` instead.

## Camera preview

The camera preview is optional on every platform: a fingertip covers the lens during a reading. The V1 plugin
does not render it; show the guidance text and the live values in your web UI. A preview view is planned; see
[Roadmap](/roadmap).

## Next steps

* [Reading lifecycle](/reading-lifecycle): what each event means and when it fires.
* [Metrics](/metrics): field definitions, units and validated accuracy.
* [Troubleshooting](/troubleshooting): permission, torch and frame-rate issues.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.