@chipmobilesdk/rn-camera 0.2.0 → 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/package.json CHANGED
@@ -1,47 +1,44 @@
1
- {
2
- "name": "@chipmobilesdk/rn-camera",
3
- "version": "0.2.0",
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
+ }
@@ -1,75 +1,74 @@
1
- ---
2
- name: sdk-rn-camera
3
- description: "Camera capture via @chipmobilesdk/rn-camera: controller, VisionCamera engine, permissions, artifact storage, hardware-free testing. Use when adding capture/preview, handling camera permissions, storing captures, or writing camera tests."
4
- metadata:
5
- package: "@chipmobilesdk/rn-camera"
6
- packageVersion: "0.1.0"
7
- user-invocable: true
8
- disable-model-invocation: false
9
- ---
10
-
11
- # @chipmobilesdk/rn-camera
12
-
13
- Camera capture built on VisionCamera, with an injectable engine, a permission port,
14
- artifact storage for captures, and a test double that needs no hardware.
15
-
16
- ## Use this when
17
-
18
- - Adding a capture or preview screen
19
- - Requesting/handling camera permissions
20
- - Persisting captured images and managing their lifetime
21
- - Writing tests for a camera flow
22
- - Answering a privacy/data-safety question about captures
23
-
24
- ## Release the camera on unmount
25
-
26
- The camera is a hardware resource. It MUST be released when the screen loses focus or
27
- unmounts — a retained camera drains battery and blocks other screens from acquiring it.
28
- Verify teardown explicitly; this is the most common defect in camera code. Prefer
29
- `useCameraController`: it disposes on unmount and survives StrictMode remounts. A
30
- disposed controller rejects `start`/`capture` with `notReady`.
31
-
32
- ## Captures expire
33
-
34
- `DEFAULT_TEMPORARY_TTL_MS` exists because cache-stored captures are temporary. If a
35
- capture must survive, pass a `destination` (app-owned) or move it out deliberately —
36
- do not assume persistence. Give the controller a `store` whose `FileSystemBridge`
37
- implements `move()`, or VisionCamera captures stay outside it and `cleanup()` cannot
38
- delete them. See `## Data behaviour` for what this means for App Privacy disclosures.
39
-
40
- ## Test without hardware
41
-
42
- `createTestCamera`, `createMemoryFileSystem` and `DEFAULT_TEST_CAPABILITIES` let camera
43
- flows be unit-tested with no device. Use these rather than mocking VisionCamera by hand —
44
- see `## Testing without hardware`.
45
-
46
- ## Public API (v0.2.0)
47
-
48
- Runtime:
49
-
50
- - `createCameraController(...)`, `useCameraController`, `useCameraState`
51
- - `createVisionCameraEngine(...)` — the default `CameraEngine`; injectable
52
- - `createVisionCameraPermissions(...)` — implements `PermissionPort`
53
- - `createArtifactStore(...)`, `createCacheArtifactStore(...)`
54
- - `CameraPreview` (+ `CameraPreviewProps`)
55
- - `CameraError`, `isCameraError`, `redact` — `redact` strips sensitive paths before logging
56
- - Testing: `createTestCamera`, `createMemoryFileSystem`, `DEFAULT_TEST_CAPABILITIES`
57
-
58
- Types: `CameraEngine`, `EngineOptions`, `PermissionPort`, `ArtifactStore`,
59
- `FileSystemBridge`, `StoreOptions`, `CameraErrorCode`, `CameraLogEvent`,
60
- `CameraLogEventType`, `CameraLogger`
61
-
62
- Always pass camera log payloads through `redact` before logging.
63
-
64
- ## Where the detail lives
65
-
66
- Read one section of `node_modules/@chipmobilesdk/rn-camera/README.md` (~340 lines):
67
-
68
- | Need | Section |
69
- |---|---|
70
- | First integration | `## Install (consuming app)`, `## Quick start` |
71
- | Engine / port / store model | `## Core concepts` |
72
- | Tests | `## Testing without hardware` |
73
- | Failure handling | `## Errors`, `## Diagnostics` |
74
- | Privacy disclosures | `## Data behaviour (for App Privacy and Data Safety)` |
75
- | Native config | `## Native capability`, `## Compatibility` |
1
+ ---
2
+ name: sdk-rn-camera
3
+ description: "Camera capture via @chipmobilesdk/rn-camera: controller, VisionCamera engine, permissions, artifact storage, hardware-free testing. Use when adding capture/preview, handling camera permissions, storing captures, or writing camera tests."
4
+ metadata:
5
+ package: "@chipmobilesdk/rn-camera"
6
+ user-invocable: true
7
+ disable-model-invocation: false
8
+ ---
9
+
10
+ # @chipmobilesdk/rn-camera
11
+
12
+ Camera capture built on VisionCamera, with an injectable engine, a permission port,
13
+ artifact storage for captures, and a test double that needs no hardware.
14
+
15
+ ## Use this when
16
+
17
+ - Adding a capture or preview screen
18
+ - Requesting/handling camera permissions
19
+ - Persisting captured images and managing their lifetime
20
+ - Writing tests for a camera flow
21
+ - Answering a privacy/data-safety question about captures
22
+
23
+ ## Release the camera on unmount
24
+
25
+ The camera is a hardware resource. It MUST be released when the screen loses focus or
26
+ unmounts — a retained camera drains battery and blocks other screens from acquiring it.
27
+ Verify teardown explicitly; this is the most common defect in camera code. Prefer
28
+ `useCameraController`: it disposes on unmount and survives StrictMode remounts. A
29
+ disposed controller rejects `start`/`capture` with `notReady`.
30
+
31
+ ## Captures expire
32
+
33
+ `DEFAULT_TEMPORARY_TTL_MS` exists because cache-stored captures are temporary. If a
34
+ capture must survive, pass a `destination` (app-owned) or move it out deliberately —
35
+ do not assume persistence. Give the controller a `store` whose `FileSystemBridge`
36
+ implements `move()`, or VisionCamera captures stay outside it and `cleanup()` cannot
37
+ delete them. See `## Data behaviour` for what this means for App Privacy disclosures.
38
+
39
+ ## Test without hardware
40
+
41
+ `createTestCamera`, `createMemoryFileSystem` and `DEFAULT_TEST_CAPABILITIES` let camera
42
+ flows be unit-tested with no device. Use these rather than mocking VisionCamera by hand —
43
+ see `## Testing without hardware`.
44
+
45
+ ## Public API
46
+
47
+ Runtime:
48
+
49
+ - `createCameraController(...)`, `useCameraController`, `useCameraState`
50
+ - `createVisionCameraEngine(...)` — the default `CameraEngine`; injectable
51
+ - `createVisionCameraPermissions(...)` — implements `PermissionPort`
52
+ - `createArtifactStore(...)`, `createCacheArtifactStore(...)`
53
+ - `CameraPreview` (+ `CameraPreviewProps`)
54
+ - `CameraError`, `isCameraError`, `redact` — `redact` strips sensitive paths before logging
55
+ - Testing: `createTestCamera`, `createMemoryFileSystem`, `DEFAULT_TEST_CAPABILITIES`
56
+
57
+ Types: `CameraEngine`, `EngineOptions`, `PermissionPort`, `ArtifactStore`,
58
+ `FileSystemBridge`, `StoreOptions`, `CameraErrorCode`, `CameraLogEvent`,
59
+ `CameraLogEventType`, `CameraLogger`
60
+
61
+ Always pass camera log payloads through `redact` before logging.
62
+
63
+ ## Where the detail lives
64
+
65
+ Read one section of `node_modules/@chipmobilesdk/rn-camera/README.md` (~340 lines):
66
+
67
+ | Need | Section |
68
+ |---|---|
69
+ | First integration | `## Install (consuming app)`, `## Quick start` |
70
+ | Engine / port / store model | `## Core concepts` |
71
+ | Tests | `## Testing without hardware` |
72
+ | Failure handling | `## Errors`, `## Diagnostics` |
73
+ | Privacy disclosures | `## Data behaviour (for App Privacy and Data Safety)` |
74
+ | Native config | `## Native capability`, `## Compatibility` |
@@ -1,149 +1,143 @@
1
- /**
2
- * Public type contract for @chipmobilesdk/rn-camera.
3
- *
4
- * Every type here is re-exported from `src/index.ts` (LIB-002/LIB-003). See
5
- * `specs/006-rn-camera-package/contracts/public-api.md` for the authoritative
6
- * contract and `data-model.md` for the rules behind each field.
7
- */
8
-
9
- /** Camera authorization status. Seven distinguishable outcomes (FR-004). */
10
- export type CameraPermissionState =
11
- | 'undetermined'
12
- | 'requesting'
13
- | 'granted'
14
- | 'denied'
15
- | 'deniedPermanently'
16
- | 'unavailable'
17
- | 'unsupported';
18
-
19
- export type CameraPosition = 'front' | 'back';
20
- export type FlashMode = 'auto' | 'on' | 'off';
21
- export type ResizeMode = 'cover' | 'contain';
22
- export type ArtifactOwnership = 'packageTemporary' | 'appOwned';
23
-
24
- export type SessionStatus =
25
- | 'idle'
26
- | 'starting'
27
- | 'active'
28
- | 'paused'
29
- | 'stopping'
30
- | 'stopped'
31
- | 'error';
32
-
33
- /**
34
- * A metadata field the platform could not determine (FR-022). Distinct from an
35
- * absent property so consumers can tell "not reported" from "not asked for".
36
- */
37
- export interface Unavailable {
38
- readonly available: false;
39
- }
40
-
41
- export interface Known<T> {
42
- readonly available: true;
43
- readonly value: T;
44
- }
45
-
46
- export type Maybe<T> = Known<T> | Unavailable;
47
-
48
- export function known<T>(value: T): Known<T> {
49
- return { available: true, value };
50
- }
51
-
52
- export function unavailable(): Unavailable {
53
- return { available: false };
54
- }
55
-
56
- /** What this device and platform actually support (FR-033). */
57
- export interface CapabilityProfile {
58
- readonly availablePositions: readonly CameraPosition[];
59
- readonly hasFlash: boolean;
60
- readonly hasTorch: boolean;
61
- readonly supportsOrientationChange: boolean;
62
- readonly hasCameraHardware: boolean;
63
- }
64
-
65
- /** Instruction to take one still photo. Carries no business context (FR-002). */
66
- export interface CaptureRequest {
67
- /** 0..1, clamped to what the device supports. */
68
- readonly quality?: number;
69
- /** Longest edge in pixels, applied within device limits. */
70
- readonly maxDimension?: number;
71
- readonly cameraPosition?: CameraPosition;
72
- readonly flashMode?: FlashMode;
73
- /**
74
- * Supplying a destination makes the result `appOwned` and the package writes
75
- * directly there. Omitting it makes the result `packageTemporary` (FR-026a).
76
- * This is the only way ownership is decided.
77
- */
78
- readonly destination?: string;
79
- }
80
-
81
- /** Result of one successful capture. Always a file reference (FR-021/FR-021a). */
82
- export interface CapturedImage {
83
- /** File URI or handle. Never a base64 string, never an in-memory buffer. */
84
- readonly uri: string;
85
- readonly mimeType: string;
86
- readonly width: number;
87
- readonly height: number;
88
- readonly timestamp: number;
89
- readonly cameraPosition: CameraPosition;
90
- readonly ownership: ArtifactOwnership;
91
- readonly effectiveQuality: number;
92
- readonly effectiveMaxDimension: Maybe<number>;
93
- readonly fileSize: Maybe<number>;
94
- readonly orientation: Maybe<number>;
95
- }
96
-
97
- /** Options accepted when starting a session. */
98
- export interface StartOptions {
99
- readonly cameraPosition?: CameraPosition;
100
- }
101
-
102
- /**
103
- * Relationship between captured-image space and on-screen preview space.
104
- * Consumed by the pure mapping functions in `geometry/coordinateMapping`.
105
- */
106
- export interface PreviewGeometry {
107
- readonly imageWidth: number;
108
- readonly imageHeight: number;
109
- readonly previewWidth: number;
110
- readonly previewHeight: number;
111
- readonly resizeMode: ResizeMode;
112
- /** Clockwise degrees: 0, 90, 180, or 270. */
113
- readonly orientation: number;
114
- readonly isMirrored: boolean;
115
- }
116
-
117
- export interface Point {
118
- readonly x: number;
119
- readonly y: number;
120
- }
121
-
122
- export interface Rect {
123
- readonly x: number;
124
- readonly y: number;
125
- readonly width: number;
126
- readonly height: number;
127
- }
128
-
129
- /** Aggregate observable state exposed by the controller. */
130
- export interface CameraState {
131
- readonly session: SessionStatus;
132
- readonly permission: CameraPermissionState;
133
- readonly capabilities: CapabilityProfile | null;
134
- readonly cameraPosition: CameraPosition;
135
- readonly flashMode: FlashMode;
136
- readonly torchEnabled: boolean;
137
- readonly capturing: boolean;
138
- readonly error: CameraErrorLike | null;
139
- }
140
-
141
- /**
142
- * Structural view of a CameraError, kept here so `CameraState` does not create
143
- * a circular import with the diagnostics module.
144
- */
145
- export interface CameraErrorLike {
146
- readonly code: string;
147
- readonly recoverable: boolean;
148
- readonly message: string;
149
- }
1
+ /**
2
+ * Public type contract for @chipmobilesdk/rn-camera.
3
+ *
4
+ * Every type here is re-exported from `src/index.ts` (LIB-002/LIB-003). See
5
+ * `specs/006-rn-camera-package/contracts/public-api.md` for the authoritative
6
+ * contract and `data-model.md` for the rules behind each field.
7
+ */
8
+
9
+ /** Camera authorization status. Seven distinguishable outcomes (FR-004). */
10
+ export type CameraPermissionState =
11
+ | 'undetermined'
12
+ | 'requesting'
13
+ | 'granted'
14
+ | 'denied'
15
+ | 'deniedPermanently'
16
+ | 'unavailable'
17
+ | 'unsupported';
18
+
19
+ export type CameraPosition = 'front' | 'back';
20
+ export type FlashMode = 'auto' | 'on' | 'off';
21
+ export type ResizeMode = 'cover' | 'contain';
22
+ export type ArtifactOwnership = 'packageTemporary' | 'appOwned';
23
+
24
+ export type SessionStatus =
25
+ 'idle' | 'starting' | 'active' | 'paused' | 'stopping' | 'stopped' | 'error';
26
+
27
+ /**
28
+ * A metadata field the platform could not determine (FR-022). Distinct from an
29
+ * absent property so consumers can tell "not reported" from "not asked for".
30
+ */
31
+ export interface Unavailable {
32
+ readonly available: false;
33
+ }
34
+
35
+ export interface Known<T> {
36
+ readonly available: true;
37
+ readonly value: T;
38
+ }
39
+
40
+ export type Maybe<T> = Known<T> | Unavailable;
41
+
42
+ export function known<T>(value: T): Known<T> {
43
+ return { available: true, value };
44
+ }
45
+
46
+ export function unavailable(): Unavailable {
47
+ return { available: false };
48
+ }
49
+
50
+ /** What this device and platform actually support (FR-033). */
51
+ export interface CapabilityProfile {
52
+ readonly availablePositions: readonly CameraPosition[];
53
+ readonly hasFlash: boolean;
54
+ readonly hasTorch: boolean;
55
+ readonly supportsOrientationChange: boolean;
56
+ readonly hasCameraHardware: boolean;
57
+ }
58
+
59
+ /** Instruction to take one still photo. Carries no business context (FR-002). */
60
+ export interface CaptureRequest {
61
+ /** 0..1, clamped to what the device supports. */
62
+ readonly quality?: number;
63
+ /** Longest edge in pixels, applied within device limits. */
64
+ readonly maxDimension?: number;
65
+ readonly cameraPosition?: CameraPosition;
66
+ readonly flashMode?: FlashMode;
67
+ /**
68
+ * Supplying a destination makes the result `appOwned` and the package writes
69
+ * directly there. Omitting it makes the result `packageTemporary` (FR-026a).
70
+ * This is the only way ownership is decided.
71
+ */
72
+ readonly destination?: string;
73
+ }
74
+
75
+ /** Result of one successful capture. Always a file reference (FR-021/FR-021a). */
76
+ export interface CapturedImage {
77
+ /** File URI or handle. Never a base64 string, never an in-memory buffer. */
78
+ readonly uri: string;
79
+ readonly mimeType: string;
80
+ readonly width: number;
81
+ readonly height: number;
82
+ readonly timestamp: number;
83
+ readonly cameraPosition: CameraPosition;
84
+ readonly ownership: ArtifactOwnership;
85
+ readonly effectiveQuality: number;
86
+ readonly effectiveMaxDimension: Maybe<number>;
87
+ readonly fileSize: Maybe<number>;
88
+ readonly orientation: Maybe<number>;
89
+ }
90
+
91
+ /** Options accepted when starting a session. */
92
+ export interface StartOptions {
93
+ readonly cameraPosition?: CameraPosition;
94
+ }
95
+
96
+ /**
97
+ * Relationship between captured-image space and on-screen preview space.
98
+ * Consumed by the pure mapping functions in `geometry/coordinateMapping`.
99
+ */
100
+ export interface PreviewGeometry {
101
+ readonly imageWidth: number;
102
+ readonly imageHeight: number;
103
+ readonly previewWidth: number;
104
+ readonly previewHeight: number;
105
+ readonly resizeMode: ResizeMode;
106
+ /** Clockwise degrees: 0, 90, 180, or 270. */
107
+ readonly orientation: number;
108
+ readonly isMirrored: boolean;
109
+ }
110
+
111
+ export interface Point {
112
+ readonly x: number;
113
+ readonly y: number;
114
+ }
115
+
116
+ export interface Rect {
117
+ readonly x: number;
118
+ readonly y: number;
119
+ readonly width: number;
120
+ readonly height: number;
121
+ }
122
+
123
+ /** Aggregate observable state exposed by the controller. */
124
+ export interface CameraState {
125
+ readonly session: SessionStatus;
126
+ readonly permission: CameraPermissionState;
127
+ readonly capabilities: CapabilityProfile | null;
128
+ readonly cameraPosition: CameraPosition;
129
+ readonly flashMode: FlashMode;
130
+ readonly torchEnabled: boolean;
131
+ readonly capturing: boolean;
132
+ readonly error: CameraErrorLike | null;
133
+ }
134
+
135
+ /**
136
+ * Structural view of a CameraError, kept here so `CameraState` does not create
137
+ * a circular import with the diagnostics module.
138
+ */
139
+ export interface CameraErrorLike {
140
+ readonly code: string;
141
+ readonly recoverable: boolean;
142
+ readonly message: string;
143
+ }
@@ -1,44 +1,41 @@
1
- /**
2
- * Capability gating (FR-033, FR-034).
3
- *
4
- * An unsupported capability must produce an `unsupportedCapability` result, not
5
- * an unexpected failure — that is what lets an app hide a control instead of
6
- * discovering at tap time that the device has no flash.
7
- */
8
-
9
- import type { CameraPosition, CapabilityProfile, FlashMode } from '../config/types';
10
- import { CameraError } from '../diagnostics/errors';
11
-
12
- export const EMPTY_CAPABILITIES: CapabilityProfile = {
13
- availablePositions: [],
14
- hasFlash: false,
15
- hasTorch: false,
16
- supportsOrientationChange: false,
17
- hasCameraHardware: false,
18
- };
19
-
20
- export function supportsPosition(
21
- profile: CapabilityProfile | null,
22
- position: CameraPosition,
23
- ): boolean {
24
- return profile?.availablePositions.includes(position) ?? false;
25
- }
26
-
27
- /** `off` is always legal — it is the absence of flash, not a use of it. */
28
- export function supportsFlashMode(
29
- profile: CapabilityProfile | null,
30
- mode: FlashMode,
31
- ): boolean {
32
- return mode === 'off' ? true : profile?.hasFlash === true;
33
- }
34
-
35
- export function supportsTorch(profile: CapabilityProfile | null): boolean {
36
- return profile?.hasTorch === true;
37
- }
38
-
39
- /** Throws the uniform unsupported error so every gate reads the same way. */
40
- export function assertSupported(supported: boolean, what: string): void {
41
- if (!supported) {
42
- throw new CameraError('unsupportedCapability', `Not supported on this device: ${what}`);
43
- }
44
- }
1
+ /**
2
+ * Capability gating (FR-033, FR-034).
3
+ *
4
+ * An unsupported capability must produce an `unsupportedCapability` result, not
5
+ * an unexpected failure — that is what lets an app hide a control instead of
6
+ * discovering at tap time that the device has no flash.
7
+ */
8
+
9
+ import type { CameraPosition, CapabilityProfile, FlashMode } from '../config/types';
10
+ import { CameraError } from '../diagnostics/errors';
11
+
12
+ export const EMPTY_CAPABILITIES: CapabilityProfile = {
13
+ availablePositions: [],
14
+ hasFlash: false,
15
+ hasTorch: false,
16
+ supportsOrientationChange: false,
17
+ hasCameraHardware: false,
18
+ };
19
+
20
+ export function supportsPosition(
21
+ profile: CapabilityProfile | null,
22
+ position: CameraPosition,
23
+ ): boolean {
24
+ return profile?.availablePositions.includes(position) ?? false;
25
+ }
26
+
27
+ /** `off` is always legal — it is the absence of flash, not a use of it. */
28
+ export function supportsFlashMode(profile: CapabilityProfile | null, mode: FlashMode): boolean {
29
+ return mode === 'off' ? true : profile?.hasFlash === true;
30
+ }
31
+
32
+ export function supportsTorch(profile: CapabilityProfile | null): boolean {
33
+ return profile?.hasTorch === true;
34
+ }
35
+
36
+ /** Throws the uniform unsupported error so every gate reads the same way. */
37
+ export function assertSupported(supported: boolean, what: string): void {
38
+ if (!supported) {
39
+ throw new CameraError('unsupportedCapability', `Not supported on this device: ${what}`);
40
+ }
41
+ }