npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chipmobilesdk/rn-camera

v0.1.1

Published

Reusable camera infrastructure for ChipMobileSdk-family React Native apps: permission-gated preview, still capture with consistent metadata, a session lifecycle that reliably releases the camera, app-controlled artifact retention, capability-gated flash a

Downloads

276

Readme

@chipmobilesdk/rn-camera

Reusable camera infrastructure for ChipMobileSdk-family React Native apps: permission-gated preview, still capture with consistent metadata, a session lifecycle that reliably releases the camera, app-controlled artifact retention, capability-gated flash and torch, coordinate mapping for app-drawn overlays, and a test adapter.

The package is infrastructure, not a feature. It contains no business logic, does not know what your photos are for, sends nothing over the network, and writes nothing to the device gallery.

License: UNLICENSED. Published publicly for use by the owner's applications; no open-source license is granted.

  • Status: 0.1.0, public npm package
  • Entrypoint: src/index.ts — the only public entrypoint
  • Native code authored here: none (see Native capability)

Compatibility

| | Supported | |---|---| | React | >= 19.2 | | React Native | >= 0.85 | | Android | minSdkVersion 24, targetSdkVersion 36 | | iOS | 15.1+ | | react-native-vision-camera | 5.1.x |

Android minSdkVersion stays at 24. Secondary sources claim VisionCamera v5 requires 26. That is wrong for 5.1.1: its gradle.properties declares VisionCamera_minSdkVersion=23 and its build.gradle resolves rootProject.ext.minSdkVersion first. Bumping to 26 would drop Android 7.0 and 7.1 devices for no reason.

Install (consuming app)

npm install @chipmobilesdk/rn-camera react-native-vision-camera react-native-nitro-modules react-native-nitro-image
cd ios && pod install && cd ..

react-native-vision-camera is a peer dependency — your app installs and links it once, exactly as it does react-native-keychain for @chipmobilesdk/rn-auth. VisionCamera 5.1.x in turn requires the Nitro modules and image packages shown above; they remain app-owned native dependencies.

If your app also uses react-native-unistyles (as the ChipMobileSdk demo app does), both it and VisionCamera depend on react-native-nitro-modules, and only one Nitro version can exist in a build. After installing, verify your theme system still works on both platforms before going further.

iOS permissions

<key>NSCameraUsageDescription</key>
<string>Explain, in your own words, why your app needs the camera.</string>

That is the only declaration the package requires. No microphone, no photo library, no location.

Android permissions

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

required="false" keeps your app installable on camera-less devices; the package reports hasCameraHardware: false there so you can render an unavailable state instead of failing.

Quick start

import React, { useCallback, useEffect, useState } from 'react';
import {
  CameraPreview,
  createVisionCameraEngine,
  createVisionCameraPermissions,
  useCameraController,
  useCameraState,
  type CapturedImage,
} from '@chipmobilesdk/rn-camera';

function CameraScreen() {
  const [photo, setPhoto] = useState<CapturedImage | null>(null);
  const controller = useCameraController({
    engine: createVisionCameraEngine(),
    permissions: createVisionCameraPermissions(),
  });
  const state = useCameraState(controller);

  useEffect(() => {
    void (async () => {
      await controller.getCapabilities();
      if (await controller.getPermission() === 'granted') {
        await controller.start();
      }
    })();
  }, [controller]);

  const requestPermission = useCallback(async () => {
    if (await controller.requestPermission() === 'granted') {
      await controller.start();
    }
  }, [controller]);

  if (state.permission !== 'granted') {
    // Your copy, your layout — the package supplies state, not UI.
    return <PermissionGate onRequest={requestPermission} state={state.permission} />;
  }

  return (
    <CameraPreview controller={controller} accessibilityLabel="Camera preview">
      <ShutterButton onPress={async () => setPhoto(await controller.capture())} />
    </CameraPreview>
  );
}

CameraPreview mounts the native feed only after controller.start() reaches the active state. getPermission() only reads state; it does not prompt or start a session. The controller hook releases the session when the screen unmounts.

Core concepts

Permission is a state, not a boolean

Seven distinguishable states, because each one needs different UI: undetermined, requesting, granted, denied, deniedPermanently, unavailable, unsupported.

getPermission() never prompts and never opens a session — safe to call on mount. Only requestPermission() prompts, and it refuses to re-prompt once the OS reports a permanent denial, reporting deniedPermanently instead so you can route the user to system settings. Revocations made in Settings while your app runs are pushed to subscribers without a restart.

Capture returns a file reference

capture() resolves to a file URI plus metadata — never a base64 string, never an in-memory buffer. Base64 inflates an image by roughly a third and holds the whole thing in JavaScript memory, and strings that size are exactly what ends up in crash logs. If you need bytes, read the file.

Metadata the platform cannot determine is explicitly marked unavailable rather than omitted:

const image = await controller.capture();
image.fileSize.available ? image.fileSize.value : 'not reported by this platform';

A bare optional could not tell "the platform does not report this" from "nobody asked", which is why the wrapper exists.

You decide what is kept

The returned ownership metadata is decided at capture time by one thing — whether you pass a destination:

| | No destination | With destination | |---|---|---| | Ownership | packageTemporary | appOwned | | Default VisionCamera write | Native temporary file | Your path, directly | | Automatic persistence | No | Controlled by your destination |

const temp = await controller.capture();                                  // package-owned
const kept = await controller.capture({ destination: '/my/app/x.jpg' });  // app-owned

// cleanup() only acts when the controller was given an ArtifactStore and the
// store reports that it owns this URI.
await controller.cleanup(temp);

The default VisionCamera adapter does not automatically move its native temporary file into the optional package store. With no store, cleanup() is a no-op and no sweep runs. To enable bounded cleanup, create an ArtifactStore with an app-supplied FileSystemBridge, pass it to the controller, and place managed files under that store's temporary directory. Initialization then sweeps only URIs the store owns, using a 24-hour default TTL. cleanup() is idempotent and refuses paths outside the store root.

The camera is released, not held

Backgrounding stops and releases the camera rather than pausing it. Both operating systems can reclaim it anyway, and holding it risks leaving the OS camera indicator lit while your app is not in front of the user. Returning to the foreground restarts the preview if the surface is still mounted.

Each controller permits one active session; starting the same controller again reports sessionConflict. The package does not implement a process-wide lock across different controller instances, so an app should mount only one active camera preview at a time.

Capability gating

Ask before you render:

const caps = await controller.getCapabilities();
if (caps.hasTorch) { /* render the torch control */ }

Invoking something the device does not have returns unsupportedCapability rather than an unexpected failure. Flash mode (auto/on/off) applies at capture time; torch is continuous during preview and is forced off whenever the session pauses, stops, or the app backgrounds.

Overlays

The package draws no bounding boxes, guide frames, or reticles — those are where apps differ. It gives you the surface (children renders above the feed) and the geometry:

<CameraPreview controller={controller} onGeometryChange={setGeometry}>
  <View style={myGuideFrame} pointerEvents="none" />
</CameraPreview>;

const inImageSpace = mapRectToImage(myGuideFrame, geometry);

The pure mapping functions account for scale, letterbox or crop, orientation, and front-camera mirroring using the geometry you supply. In the current CameraPreview, onGeometryChange reports nominal image dimensions (1920 × 1080), orientation 0, and mirroring from camera position; it does not yet read the selected native capture format. Use capture metadata or app-owned measured geometry when pixel-exact alignment is required.

Quality and dimensions

quality and maxDimension are normalized into effectiveQuality and effectiveMaxDimension metadata. The current VisionCamera 5.1 adapter does not yet resize or re-encode the saved image, so these fields are hints rather than a guarantee about output bytes. Perform app-side image processing when an exact upload size or quality is required.

Testing without hardware

createTestCamera() returns real implementations of the same published ports the production adapters implement — not a parallel mock, so it cannot drift from the contract without a compile error.

import { createCameraController, createTestCamera } from '@chipmobilesdk/rn-camera';

const camera = createTestCamera({ permission: 'granted' });
const controller = createCameraController({
  engine: camera.engine,
  permissions: camera.permissions,
  store: camera.store,
  subscribeToAppState: camera.subscribeToAppState,
  sweepOnInit: false,
});

await controller.requestPermission();
await controller.getCapabilities();
await controller.start();

const image = await controller.capture();

// Drive any failure path:
camera.setCaptureError('captureFailed');
camera.setPermission('deniedPermanently');
camera.setAppState('background');
camera.emitInterruption();

Add a module mapping so an accidental import of the native module resolves in Jest:

moduleNameMapper: {
  '^react-native-vision-camera$': '<rootDir>/__tests__/__mocks__/react-native-vision-camera.ts',
}

Errors

Every failure is a CameraError with a code and a recoverable flag, so you branch on the code rather than string-matching a message:

permissionDenied, cameraUnavailable, cameraBlocked, cameraInUse, sessionConflict, notReady, captureInProgress, captureFailed, storageFailure, unsupportedCapability, interrupted.

A double-tapped shutter produces captureInProgress on the second call. The package does not queue (which would give the user two photos they never asked for) and does not coalesce (which would hide a deliberate second capture behind an accidental one).

Diagnostics

Logging is off by default, and event bodies carry codes and states only — never file paths or image data:

createCameraController({ logger: { log: event => myLogger.debug(event) } });

A redact() helper is exported for scrubbing paths and payload-shaped strings from anything else you log.

Data behaviour (for App Privacy and Data Safety)

Use this when completing your store declarations:

| Question | Answer for this package | |---|---| | What data is involved? | Camera imagery generated on-device by explicit user action, plus technical metadata (dimensions, mime type, size, orientation, timestamp, camera position) | | Transmitted off device? | No. The package makes no network requests of any kind | | Shared with third parties? | No | | Stored persistently? | A capture is a file: the default adapter uses VisionCamera's native temporary location unless the app supplies a destination. The package has no history database and does not write to the gallery | | Location attached? | No. No location data is read and no location permission is requested | | Written to the photo library? | No | | Tracking, ads, identifiers? | None | | On-device AI or inference? | None in this phase |

If your app uploads captured images off-device, that collection is yours to declare — the package cannot know you do it. The same applies to moderation and reporting obligations if captures become user-generated content.

Native capability

This package authors zero native source. Camera capability arrives entirely through the react-native-vision-camera peer dependency, mirroring how @chipmobilesdk/rn-auth obtains Keychain and OIDC capability.

The ChipMobileSdk constitution prohibits native code in shared libraries by default. A camera cannot be implemented in TypeScript, so this is the exception in its narrowest available form: a maintained MIT library the consuming app installs, wrapped behind port interfaces this package owns.

Reserved: local frame processing (not implemented)

Phase one runs no inference, emits no AI-derived labels, and sends no frame anywhere. The extension point is documented now so the later local-AI phase can arrive as an additive minor release without disturbing anything above.

The reserved contract:

// NOT EXPORTED. Reserved shape for the local-AI phase.
interface FrameProcessorOptions {
  maxFps: number;                  // frame-rate cap, to protect the UI thread and battery
  inferenceMaxDimension: number;   // lower than preview resolution
  onFrame: (frame: ReservedFrame) => void;
  backpressure: 'dropLatest' | 'dropOldest';
  // Automatically paused when the app backgrounds. Frames never leave the device.
}

Still capture and real-time frame processing stay strictly separate.

Public API

Everything is exported from @chipmobilesdk/rn-camera. Modules under core/, native/, permissions/, storage/, geometry/, diagnostics/, and testing/ are private — do not deep-import them.

  • Controller and state: createCameraController, CameraController, CameraControllerOptions, useCameraController, useCameraState, CameraState, AppStatus, DEFAULT_TEMPORARY_TTL_MS
  • Preview and capture: CameraPreview, CameraPreviewProps, CaptureRequest, CapturedImage, StartOptions and the camera/session/capability types
  • Ports and adapters: CameraEngine, EngineOptions, createVisionCameraEngine, PermissionPort, createVisionCameraPermissions, ArtifactStore, FileSystemBridge, StoreOptions, createCacheArtifactStore, createArtifactStore
  • Mapping: mapPointToImage, mapPointToPreview, mapRectToImage, mapRectToPreview, plus Point, Rect, and PreviewGeometry
  • Diagnostics: CameraError, CameraErrorCode, isCameraError, redact, and the logger event/type contracts
  • Testing: createTestCamera, createMemoryFileSystem, DEFAULT_TEST_CAPABILITIES, and their configuration/handle types
  • Metadata helpers: known, unavailable, Known, Unavailable, Maybe

Stable for semver: types, controller, preview, mapping, errors. Experimental and may change in a minor: the diagnostics logger shape and the test adapter configuration.

License

UNLICENSED. The npm tarball is publicly downloadable, but no open-source license or permission to copy, modify, or redistribute is granted.

Demo and validation

The demo integration lives in src/screens/CameraDemoScreen.tsx. Run the package commands from the repository root (camera currently has no root-level shortcut):

npm run typecheck --workspace @chipmobilesdk/rn-camera
npm run pack:dry-run --workspace @chipmobilesdk/rn-camera