> ## 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 while `motionBreathing` is on.

    **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;

      const status = await NeurofitVitals.activate({ licenseKey });   // rejects on an invalid key
      status.warnings.forEach((w) => console.warn('License warning:', w));

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

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

    Rejections carry `code` (the native error case: `licenseInvalid`, `licenseNotValidForApp`,
    `licenseUpdatesExpired`, `cameraDenied`, ...) and `message`. See [License keys](/license-keys).
  </Step>

  <Step title="Run a reading">
    `createSession` returns a `sessionId`. Add listeners **before** calling `start`; the plugin does not buffer
    events. Every payload carries `event` and `sessionId`.

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

    export async function runReading(ui: {
      guidance(g: string, startProgress: number): void;
      live(progress: number, hrBpm: number | null, rmssdMs: number | null): void;
      notice(text: string): void;
    }): Promise<VitalsResult> {
      const { sessionId } = await NeurofitVitals.createSession({
        config: { readingDuration: 60, autoRestartAfterCancel: true, motionBreathing: true },
      });

      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.progress, e.hrBpm, e.rmssdMs)),   // rmssdMs from ~20 s
        NeurofitVitals.addListener('cancelled', (e) => ui.notice(`Reading restarted: ${e.reason}`)),
        NeurofitVitals.addListener('reconfiguringCamera', (e) => {
          if (e.lowPowerMode) ui.notice('Please turn off Low Power Mode.');
        }),
        NeurofitVitals.addListener('completed', (e) => resolveResult(e.result)),
        NeurofitVitals.addListener('failed', (e) => rejectResult(e.error)),
      ]);

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

    `NeurofitVitals.cancelReading({ sessionId, reason: 'user_tapped_cancel' })` cancels the current attempt while
    measuring (the session returns to positioning; ignored while positioning). `stop({ sessionId })` tears the
    camera down and releases the handle; it is idempotent, so a second `stop` resolves. A later `start` or
    `cancelReading` with a released handle rejects with the wrapper-level code `sessionNotFound`.
    `removeAllListeners()` clears every listener at once when your reading screen is destroyed.

    `start` rejects with the native error (`notActivated`, `alreadyRunning`, `cameraDenied`, `cameraRestricted`,
    `unsupportedDevice`, on iOS also `cameraUnavailable`); 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: backgrounding (iOS) or the host activity's
`ON_STOP` (Android) during positioning or measuring ends the session with a `failed` event whose `error.code` is
`interrupted`. Lock the reading screen's orientation while a session runs (`@capacitor/screen-orientation`) and
keep the screen awake (`@capacitor-community/keep-awake`).

## Plugin interface

```typescript definitions.ts theme={null}
export interface NeurofitVitalsPlugin {
  activate(options: { licenseKey: string }): Promise<LicenseStatus>;
  deviceSupport(): Promise<DeviceSupport>;
  requestCameraPermission(): Promise<{ granted: boolean }>;
  createSession(options?: { config?: VitalsConfiguration }): Promise<{ sessionId: string }>;
  start(options: { sessionId: string }): Promise<void>;
  cancelReading(options: { sessionId: string; reason?: string }): Promise<void>;   // reason defaults to "host"
  stop(options: { sessionId: string }): Promise<void>;
  addListener<Name extends VitalsEventName>(
    eventName: Name,
    listenerFunc: (event: VitalsEventPayload<Name>) => void,
  ): Promise<PluginListenerHandle>;
  removeAllListeners(): Promise<void>;
}
```

Types (`LicenseStatus`, `DeviceSupport`, `VitalsConfiguration`, `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. On the web, rejections carry
Capacitor's `ExceptionCode.Unavailable` (`UNAVAILABLE`) rather than a native case name.

## 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.