@chipmobilesdk/rn-camera 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,30 +1,30 @@
1
- # @chipmobilesdk/rn-camera
2
-
3
- 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.
4
-
1
+ # @chipmobilesdk/rn-camera
2
+
3
+ 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.
4
+
5
5
  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.
6
6
 
7
7
  > **License:** `UNLICENSED`. Published publicly for use by the owner's
8
8
  > applications; no open-source license is granted.
9
-
10
- - **Status**: `0.1.0`, public npm package
11
- - **Entrypoint**: `src/index.ts` — the only public entrypoint
12
- - **Native code authored here**: none (see [Native capability](#native-capability))
13
-
14
- ## Compatibility
15
-
16
- | | Supported |
17
- |---|---|
18
- | React | >= 19.2 |
19
- | React Native | >= 0.85 |
20
- | Android | `minSdkVersion` **24**, `targetSdkVersion` 36 |
21
- | iOS | 15.1+ |
22
- | `react-native-vision-camera` | 5.1.x |
23
-
24
- > **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.
25
-
26
- ## Install (consuming app)
27
-
9
+
10
+ - **Status**: public npm package; version in [CHANGELOG](CHANGELOG.md)
11
+ - **Entrypoint**: `src/index.ts` — the only public entrypoint
12
+ - **Native code authored here**: none (see [Native capability](#native-capability))
13
+
14
+ ## Compatibility
15
+
16
+ | | Supported |
17
+ |---|---|
18
+ | React | >= 19.2 |
19
+ | React Native | >= 0.85 |
20
+ | Android | `minSdkVersion` **24**, `targetSdkVersion` 36 |
21
+ | iOS | 15.1+ |
22
+ | `react-native-vision-camera` | 5.1.x |
23
+
24
+ > **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.
25
+
26
+ ## Install (consuming app)
27
+
28
28
  ```bash
29
29
  npm install @chipmobilesdk/rn-camera react-native-vision-camera react-native-nitro-modules react-native-nitro-image
30
30
  cd ios && pod install && cd ..
@@ -33,29 +33,29 @@ cd ios && pod install && cd ..
33
33
  `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`.
34
34
  VisionCamera 5.1.x in turn requires the Nitro modules and image packages shown
35
35
  above; they remain app-owned native dependencies.
36
-
37
- > **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.
38
-
39
- ### iOS permissions
40
-
41
- ```xml
42
- <key>NSCameraUsageDescription</key>
43
- <string>Explain, in your own words, why your app needs the camera.</string>
44
- ```
45
-
46
- That is the only declaration the package requires. No microphone, no photo library, no location.
47
-
48
- ### Android permissions
49
-
50
- ```xml
51
- <uses-permission android:name="android.permission.CAMERA" />
52
- <uses-feature android:name="android.hardware.camera" android:required="false" />
53
- ```
54
-
55
- `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.
56
-
57
- ## Quick start
58
-
36
+
37
+ > **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.
38
+
39
+ ### iOS permissions
40
+
41
+ ```xml
42
+ <key>NSCameraUsageDescription</key>
43
+ <string>Explain, in your own words, why your app needs the camera.</string>
44
+ ```
45
+
46
+ That is the only declaration the package requires. No microphone, no photo library, no location.
47
+
48
+ ### Android permissions
49
+
50
+ ```xml
51
+ <uses-permission android:name="android.permission.CAMERA" />
52
+ <uses-feature android:name="android.hardware.camera" android:required="false" />
53
+ ```
54
+
55
+ `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.
56
+
57
+ ## Quick start
58
+
59
59
  ```tsx
60
60
  import React, { useCallback, useEffect, useState } from 'react';
61
61
  import {
@@ -66,7 +66,7 @@ import {
66
66
  useCameraState,
67
67
  type CapturedImage,
68
68
  } from '@chipmobilesdk/rn-camera';
69
-
69
+
70
70
  function CameraScreen() {
71
71
  const [photo, setPhoto] = useState<CapturedImage | null>(null);
72
72
  const controller = useCameraController({
@@ -94,12 +94,12 @@ function CameraScreen() {
94
94
  // Your copy, your layout — the package supplies state, not UI.
95
95
  return <PermissionGate onRequest={requestPermission} state={state.permission} />;
96
96
  }
97
-
98
- return (
99
- <CameraPreview controller={controller} accessibilityLabel="Camera preview">
100
- <ShutterButton onPress={async () => setPhoto(await controller.capture())} />
101
- </CameraPreview>
102
- );
97
+
98
+ return (
99
+ <CameraPreview controller={controller} accessibilityLabel="Camera preview">
100
+ <ShutterButton onPress={async () => setPhoto(await controller.capture())} />
101
+ </CameraPreview>
102
+ );
103
103
  }
104
104
  ```
105
105
 
@@ -107,28 +107,28 @@ function CameraScreen() {
107
107
  the `active` state. `getPermission()` only reads state; it does not prompt or
108
108
  start a session. The controller hook releases the session when the screen
109
109
  unmounts.
110
-
111
- ## Core concepts
112
-
113
- ### Permission is a state, not a boolean
114
-
115
- Seven distinguishable states, because each one needs different UI: `undetermined`, `requesting`, `granted`, `denied`, `deniedPermanently`, `unavailable`, `unsupported`.
116
-
117
- `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.
118
-
119
- ### Capture returns a file reference
120
-
121
- `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.
122
-
123
- Metadata the platform cannot determine is **explicitly marked unavailable** rather than omitted:
124
-
125
- ```ts
126
- const image = await controller.capture();
127
- image.fileSize.available ? image.fileSize.value : 'not reported by this platform';
128
- ```
129
-
130
- A bare optional could not tell "the platform does not report this" from "nobody asked", which is why the wrapper exists.
131
-
110
+
111
+ ## Core concepts
112
+
113
+ ### Permission is a state, not a boolean
114
+
115
+ Seven distinguishable states, because each one needs different UI: `undetermined`, `requesting`, `granted`, `denied`, `deniedPermanently`, `unavailable`, `unsupported`.
116
+
117
+ `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.
118
+
119
+ ### Capture returns a file reference
120
+
121
+ `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.
122
+
123
+ Metadata the platform cannot determine is **explicitly marked unavailable** rather than omitted:
124
+
125
+ ```ts
126
+ const image = await controller.capture();
127
+ image.fileSize.available ? image.fileSize.value : 'not reported by this platform';
128
+ ```
129
+
130
+ A bare optional could not tell "the platform does not report this" from "nobody asked", which is why the wrapper exists.
131
+
132
132
  ### You decide what is kept
133
133
 
134
134
  The returned ownership metadata is decided at capture time by one thing —
@@ -139,56 +139,79 @@ whether you pass a `destination`:
139
139
  | Ownership | `packageTemporary` | `appOwned` |
140
140
  | Default VisionCamera write | Native temporary file | Your path, directly |
141
141
  | Automatic persistence | No | Controlled by your destination |
142
-
143
- ```ts
144
- const temp = await controller.capture(); // package-owned
145
- const kept = await controller.capture({ destination: '/my/app/x.jpg' }); // app-owned
146
-
142
+
143
+ ```ts
144
+ const temp = await controller.capture(); // package-owned
145
+ const kept = await controller.capture({ destination: '/my/app/x.jpg' }); // app-owned
146
+
147
147
  // cleanup() only acts when the controller was given an ArtifactStore and the
148
148
  // store reports that it owns this URI.
149
149
  await controller.cleanup(temp);
150
150
  ```
151
151
 
152
- The default VisionCamera adapter does not automatically move its native
153
- temporary file into the optional package store. With no `store`, `cleanup()` is
154
- a no-op and no sweep runs. To enable bounded cleanup, create an
155
- `ArtifactStore` with an app-supplied `FileSystemBridge`, pass it to the
156
- controller, and place managed files under that store's temporary directory.
157
- Initialization then sweeps only URIs the store owns, using a 24-hour default
158
- TTL. `cleanup()` is idempotent and refuses paths outside the store root.
159
-
160
- ### The camera is released, not held
161
-
162
- 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.
163
-
152
+ With a `store`, the controller moves every package-temporary capture out of
153
+ VisionCamera's own temporary directory into the store (`ArtifactStore.adopt`), so
154
+ `cleanup()` can delete it and the sweep can reach orphans. That move needs a
155
+ `FileSystemBridge` that implements `move()`; the app supplies the bridge, because
156
+ the package takes no filesystem dependency of its own:
157
+
158
+ ```ts
159
+ import * as RNFS from '@dr.pogodin/react-native-fs';
160
+
161
+ const store = createArtifactStore({
162
+ cacheDirectory: () => RNFS.CachesDirectoryPath,
163
+ exists: RNFS.exists,
164
+ unlink: RNFS.unlink,
165
+ list: async dir => (await RNFS.readDir(dir)).filter(e => e.isFile()).map(e => e.path),
166
+ createdAt: async path => (await RNFS.stat(path)).mtime.getTime(),
167
+ ensureDirectory: async dir => { if (!(await RNFS.exists(dir))) await RNFS.mkdir(dir); },
168
+ move: RNFS.moveFile,
169
+ });
170
+ ```
171
+
172
+ With no `store`, captures stay in VisionCamera's directory, `cleanup()` is a no-op,
173
+ no sweep runs, and an opted-in logger reports `unmanaged:no-store` once. The sweep
174
+ runs when the controller is first used, uses a 24-hour default TTL, touches only
175
+ URIs the store owns, and spares captures adopted by the running process.
176
+ `cleanup()` is idempotent and refuses paths outside the store root.
177
+
178
+ ### The camera is released, not held
179
+
180
+ 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 only if backgrounding interrupted a running session, the surface is still mounted, and permission is still granted — a camera the user stopped stays stopped.
181
+
164
182
  Each controller permits one active session; starting the same controller again
165
- reports `sessionConflict`. The package does not implement a process-wide lock
183
+ reports `sessionConflict`.
184
+
185
+ `useCameraController` disposes its controller on unmount and replaces it if React
186
+ remounts the component (StrictMode does this in development). A disposed controller
187
+ rejects `start`, `resume` and `capture` with `notReady`; `stop` and `cleanup` stay
188
+ safe to call. The package does not implement a process-wide lock
166
189
  across different controller instances, so an app should mount only one active
167
190
  camera preview at a time.
168
-
169
- ### Capability gating
170
-
171
- Ask before you render:
172
-
173
- ```ts
174
- const caps = await controller.getCapabilities();
175
- if (caps.hasTorch) { /* render the torch control */ }
176
- ```
177
-
178
- 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.
179
-
180
- ### Overlays
181
-
182
- 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:
183
-
184
- ```tsx
185
- <CameraPreview controller={controller} onGeometryChange={setGeometry}>
186
- <View style={myGuideFrame} pointerEvents="none" />
187
- </CameraPreview>;
188
-
189
- const inImageSpace = mapRectToImage(myGuideFrame, geometry);
190
- ```
191
-
191
+
192
+ ### Capability gating
193
+
194
+ Ask before you render:
195
+
196
+ ```ts
197
+ const caps = await controller.getCapabilities();
198
+ if (caps.hasTorch) { /* render the torch control */ }
199
+ ```
200
+
201
+ 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.
202
+
203
+ ### Overlays
204
+
205
+ 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:
206
+
207
+ ```tsx
208
+ <CameraPreview controller={controller} onGeometryChange={setGeometry}>
209
+ <View style={myGuideFrame} pointerEvents="none" />
210
+ </CameraPreview>;
211
+
212
+ const inImageSpace = mapRectToImage(myGuideFrame, geometry);
213
+ ```
214
+
192
215
  The pure mapping functions account for scale, letterbox or crop, orientation,
193
216
  and front-camera mirroring using the geometry you supply. In the current
194
217
  `CameraPreview`, `onGeometryChange` reports nominal image dimensions
@@ -198,113 +221,113 @@ app-owned measured geometry when pixel-exact alignment is required.
198
221
 
199
222
  ### Quality and dimensions
200
223
 
201
- `quality` and `maxDimension` are normalized into `effectiveQuality` and
202
- `effectiveMaxDimension` metadata. The current VisionCamera 5.1 adapter does
203
- not yet resize or re-encode the saved image, so these fields are hints rather
204
- than a guarantee about output bytes. Perform app-side image processing when an
205
- exact upload size or quality is required.
206
-
207
- ## Testing without hardware
208
-
209
- `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.
210
-
211
- ```ts
212
- import { createCameraController, createTestCamera } from '@chipmobilesdk/rn-camera';
213
-
214
- const camera = createTestCamera({ permission: 'granted' });
215
- const controller = createCameraController({
216
- engine: camera.engine,
217
- permissions: camera.permissions,
218
- store: camera.store,
219
- subscribeToAppState: camera.subscribeToAppState,
220
- sweepOnInit: false,
221
- });
222
-
223
- await controller.requestPermission();
224
- await controller.getCapabilities();
225
- await controller.start();
226
-
227
- const image = await controller.capture();
228
-
229
- // Drive any failure path:
230
- camera.setCaptureError('captureFailed');
231
- camera.setPermission('deniedPermanently');
232
- camera.setAppState('background');
233
- camera.emitInterruption();
234
- ```
235
-
236
- Add a module mapping so an accidental import of the native module resolves in Jest:
237
-
238
- ```js
239
- moduleNameMapper: {
240
- '^react-native-vision-camera$': '<rootDir>/__tests__/__mocks__/react-native-vision-camera.ts',
241
- }
242
- ```
243
-
244
- ## Errors
245
-
246
- Every failure is a `CameraError` with a `code` and a `recoverable` flag, so you branch on the code rather than string-matching a message:
247
-
248
- `permissionDenied`, `cameraUnavailable`, `cameraBlocked`, `cameraInUse`, `sessionConflict`, `notReady`, `captureInProgress`, `captureFailed`, `storageFailure`, `unsupportedCapability`, `interrupted`.
249
-
250
- 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).
251
-
252
- ## Diagnostics
253
-
254
- Logging is **off by default**, and event bodies carry codes and states only — never file paths or image data:
255
-
256
- ```ts
257
- createCameraController({ logger: { log: event => myLogger.debug(event) } });
258
- ```
259
-
260
- A `redact()` helper is exported for scrubbing paths and payload-shaped strings from anything else you log.
261
-
262
- ## Data behaviour (for App Privacy and Data Safety)
263
-
264
- Use this when completing your store declarations:
265
-
266
- | Question | Answer for this package |
267
- |---|---|
268
- | What data is involved? | Camera imagery generated on-device by explicit user action, plus technical metadata (dimensions, mime type, size, orientation, timestamp, camera position) |
269
- | Transmitted off device? | **No.** The package makes no network requests of any kind |
270
- | Shared with third parties? | **No** |
224
+ The VisionCamera 5.1 adapter configures the photo output once, at quality `0.9`,
225
+ and does not resize. Each capture therefore reports `effectiveQuality: 0.9` and an
226
+ unavailable `effectiveMaxDimension`, whatever the request asked for; the request
227
+ hints are not applied. Perform app-side image processing when an exact upload size
228
+ or quality is required.
229
+
230
+ ## Testing without hardware
231
+
232
+ `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.
233
+
234
+ ```ts
235
+ import { createCameraController, createTestCamera } from '@chipmobilesdk/rn-camera';
236
+
237
+ const camera = createTestCamera({ permission: 'granted' });
238
+ const controller = createCameraController({
239
+ engine: camera.engine,
240
+ permissions: camera.permissions,
241
+ store: camera.store,
242
+ subscribeToAppState: camera.subscribeToAppState,
243
+ sweepOnInit: false,
244
+ });
245
+
246
+ await controller.requestPermission();
247
+ await controller.getCapabilities();
248
+ await controller.start();
249
+
250
+ const image = await controller.capture();
251
+
252
+ // Drive any failure path:
253
+ camera.setCaptureError('captureFailed');
254
+ camera.setPermission('deniedPermanently');
255
+ camera.setAppState('background');
256
+ camera.emitInterruption();
257
+ ```
258
+
259
+ Add a module mapping so an accidental import of the native module resolves in Jest:
260
+
261
+ ```js
262
+ moduleNameMapper: {
263
+ '^react-native-vision-camera$': '<rootDir>/__tests__/__mocks__/react-native-vision-camera.ts',
264
+ }
265
+ ```
266
+
267
+ ## Errors
268
+
269
+ Every failure is a `CameraError` with a `code` and a `recoverable` flag, so you branch on the code rather than string-matching a message:
270
+
271
+ `permissionDenied`, `cameraUnavailable`, `cameraBlocked`, `cameraInUse`, `sessionConflict`, `notReady`, `captureInProgress`, `captureFailed`, `storageFailure`, `unsupportedCapability`, `interrupted`.
272
+
273
+ 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).
274
+
275
+ ## Diagnostics
276
+
277
+ Logging is **off by default**, and event bodies carry codes and states only — never file paths or image data:
278
+
279
+ ```ts
280
+ createCameraController({ logger: { log: event => myLogger.debug(event) } });
281
+ ```
282
+
283
+ A `redact()` helper is exported for scrubbing paths and payload-shaped strings from anything else you log.
284
+
285
+ ## Data behaviour (for App Privacy and Data Safety)
286
+
287
+ Use this when completing your store declarations:
288
+
289
+ | Question | Answer for this package |
290
+ |---|---|
291
+ | What data is involved? | Camera imagery generated on-device by explicit user action, plus technical metadata (dimensions, mime type, size, orientation, timestamp, camera position) |
292
+ | Transmitted off device? | **No.** The package makes no network requests of any kind |
293
+ | Shared with third parties? | **No** |
271
294
  | 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 |
272
- | Location attached? | **No.** No location data is read and no location permission is requested |
273
- | Written to the photo library? | **No** |
274
- | Tracking, ads, identifiers? | **None** |
275
- | On-device AI or inference? | **None in this phase** |
276
-
277
- **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.
278
-
279
- ## Native capability
280
-
281
- 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.
282
-
283
- 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.
284
-
285
- ## Reserved: local frame processing (not implemented)
286
-
287
- 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.
288
-
289
- The reserved contract:
290
-
291
- ```ts
292
- // NOT EXPORTED. Reserved shape for the local-AI phase.
293
- interface FrameProcessorOptions {
294
- maxFps: number; // frame-rate cap, to protect the UI thread and battery
295
- inferenceMaxDimension: number; // lower than preview resolution
296
- onFrame: (frame: ReservedFrame) => void;
297
- backpressure: 'dropLatest' | 'dropOldest';
298
- // Automatically paused when the app backgrounds. Frames never leave the device.
299
- }
300
- ```
301
-
302
- Still capture and real-time frame processing stay strictly separate.
303
-
304
- ## Public API
305
-
306
- Everything is exported from `@chipmobilesdk/rn-camera`. Modules under `core/`, `native/`, `permissions/`, `storage/`, `geometry/`, `diagnostics/`, and `testing/` are private — do not deep-import them.
307
-
295
+ | Location attached? | **No.** No location data is read and no location permission is requested |
296
+ | Written to the photo library? | **No** |
297
+ | Tracking, ads, identifiers? | **None** |
298
+ | On-device AI or inference? | **None in this phase** |
299
+
300
+ **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.
301
+
302
+ ## Native capability
303
+
304
+ 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.
305
+
306
+ 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.
307
+
308
+ ## Reserved: local frame processing (not implemented)
309
+
310
+ 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.
311
+
312
+ The reserved contract:
313
+
314
+ ```ts
315
+ // NOT EXPORTED. Reserved shape for the local-AI phase.
316
+ interface FrameProcessorOptions {
317
+ maxFps: number; // frame-rate cap, to protect the UI thread and battery
318
+ inferenceMaxDimension: number; // lower than preview resolution
319
+ onFrame: (frame: ReservedFrame) => void;
320
+ backpressure: 'dropLatest' | 'dropOldest';
321
+ // Automatically paused when the app backgrounds. Frames never leave the device.
322
+ }
323
+ ```
324
+
325
+ Still capture and real-time frame processing stay strictly separate.
326
+
327
+ ## Public API
328
+
329
+ Everything is exported from `@chipmobilesdk/rn-camera`. Modules under `core/`, `native/`, `permissions/`, `storage/`, `geometry/`, `diagnostics/`, and `testing/` are private — do not deep-import them.
330
+
308
331
  - Controller and state: `createCameraController`, `CameraController`,
309
332
  `CameraControllerOptions`, `useCameraController`, `useCameraState`,
310
333
  `CameraState`, `AppStatus`, `DEFAULT_TEMPORARY_TTL_MS`
@@ -321,9 +344,9 @@ Everything is exported from `@chipmobilesdk/rn-camera`. Modules under `core/`, `
321
344
  - Testing: `createTestCamera`, `createMemoryFileSystem`,
322
345
  `DEFAULT_TEST_CAPABILITIES`, and their configuration/handle types
323
346
  - Metadata helpers: `known`, `unavailable`, `Known`, `Unavailable`, `Maybe`
324
-
325
- Stable for semver: types, controller, preview, mapping, errors. Experimental and may change in a minor: the diagnostics logger shape and the test adapter configuration.
326
-
347
+
348
+ Stable for semver: types, controller, preview, mapping, errors. Experimental and may change in a minor: the diagnostics logger shape and the test adapter configuration.
349
+
327
350
  ## License
328
351
 
329
352
  `UNLICENSED`. The npm tarball is publicly downloadable, but no open-source
@@ -337,5 +360,5 @@ shortcut):
337
360
 
338
361
  ```bash
339
362
  npm run typecheck --workspace @chipmobilesdk/rn-camera
340
- npm run pack:dry-run --workspace @chipmobilesdk/rn-camera
363
+ npm run pack:check --workspace @chipmobilesdk/rn-camera
341
364
  ```
package/package.json CHANGED
@@ -1,47 +1,44 @@
1
- {
2
- "name": "@chipmobilesdk/rn-camera",
3
- "version": "0.1.1",
4
- "private": false,
5
- "license": "UNLICENSED",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.ts",
11
- "react-native": "./src/index.ts",
12
- "default": "./src/index.ts"
13
- }
14
- },
15
- "files": [
16
- "src/index.ts",
17
- "src/config",
18
- "src/core",
19
- "src/diagnostics",
20
- "src/geometry",
21
- "src/native",
22
- "src/permissions",
23
- "src/react",
24
- "src/storage",
25
- "src/testing",
26
- "skills",
27
- "README.md",
28
- "CHANGELOG.md"
29
- ],
30
- "sideEffects": false,
31
- "scripts": {
32
- "typecheck": "tsc -p tsconfig.json --noEmit",
33
- "pack:dry-run": "npm pack --dry-run"
34
- },
35
- "peerDependencies": {
36
- "react": ">=19",
37
- "react-native": ">=0.85",
38
- "react-native-vision-camera": ">=5.1.1"
39
- },
40
- "engines": {
41
- "node": ">=22.11.0"
42
- },
43
- "publishConfig": {
44
- "access": "public",
45
- "registry": "https://registry.npmjs.org/"
46
- }
47
- }
1
+ {
2
+ "name": "@chipmobilesdk/rn-camera",
3
+ "version": "0.2.1",
4
+ "private": false,
5
+ "license": "UNLICENSED",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "react-native": "./src/index.ts",
12
+ "default": "./src/index.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "src/index.ts",
17
+ "src/config",
18
+ "src/core",
19
+ "src/diagnostics",
20
+ "src/geometry",
21
+ "src/native",
22
+ "src/permissions",
23
+ "src/react",
24
+ "src/storage",
25
+ "src/testing",
26
+ "skills",
27
+ "README.md",
28
+ "CHANGELOG.md"
29
+ ],
30
+ "sideEffects": false,
31
+ "scripts": {
32
+ "typecheck": "tsc -p tsconfig.json --noEmit",
33
+ "pack:check": "npm pack --dry-run"
34
+ },
35
+ "peerDependencies": {
36
+ "react": ">=19",
37
+ "react-native": ">=0.85",
38
+ "react-native-vision-camera": ">=5.1.1"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "registry": "https://registry.npmjs.org/"
43
+ }
44
+ }