@flighthq/webcam 0.4.0-next.1898.266773a → 0.4.0-next.1908.751021c

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/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
1
+ export { explainWebcamBackend, createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
1
+ export { explainWebcamBackend, createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
package/dist/webcam.d.ts CHANGED
@@ -1,8 +1,13 @@
1
+ import type { BackendExplanation } from '@flighthq/types/contract';
1
2
  import type { WebcamBackend, WebcamCaptureOptions, WebcamPhoto, WebcamVideo } from '@flighthq/types/contract';
2
3
  export declare function createWebWebcamBackend(): WebcamBackend;
4
+ export declare function explainWebcamBackend(): BackendExplanation;
3
5
  export declare function getWebcamBackend(): WebcamBackend;
6
+ export declare function installWebcamHostBackend(backend: WebcamBackend): void;
7
+ export declare function observeWebcamHostResult(operation: string, succeeded: boolean): void;
4
8
  export declare function recordWebcamVideo(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamVideo | null>;
5
9
  export declare function requestWebcamPermission(): Promise<boolean>;
10
+ export declare function resetWebcamBackendForTest(): void;
6
11
  export declare function selectWebcamImage(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
7
12
  export declare function setWebcamBackend(backend: WebcamBackend | null): void;
8
13
  export declare function takeWebcamPhoto(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"webcam.d.ts","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAK9G,wBAAgB,sBAAsB,IAAI,aAAa,CAsFtD;AAGD,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAGD,wBAAgB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAEpE;AAGD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAErG"}
1
+ {"version":3,"file":"webcam.d.ts","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAK9G,wBAAgB,sBAAsB,IAAI,aAAa,CAwGtD;AAED,wBAAgB,oBAAoB,IAAI,kBAAkB,CAazD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAMrE;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKnF;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAGD,wBAAgB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAKhD;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAEpE;AAGD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAErG"}
package/dist/webcam.js CHANGED
@@ -6,6 +6,7 @@ export function createWebWebcamBackend() {
6
6
  capture(options) {
7
7
  return new Promise((resolve) => {
8
8
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
9
+ observeWebcamHostResult('capture', false);
9
10
  resolve(null);
10
11
  return;
11
12
  }
@@ -23,6 +24,7 @@ export function createWebWebcamBackend() {
23
24
  }
24
25
  const reader = new FileReader();
25
26
  reader.onload = () => {
27
+ observeWebcamHostResult('capture', true);
26
28
  resolve({
27
29
  dataUrl: typeof reader.result === 'string' ? reader.result : '',
28
30
  width: 0,
@@ -30,12 +32,16 @@ export function createWebWebcamBackend() {
30
32
  format: file.type,
31
33
  });
32
34
  };
33
- reader.onerror = () => resolve(null);
35
+ reader.onerror = () => {
36
+ observeWebcamHostResult('capture', false);
37
+ resolve(null);
38
+ };
34
39
  reader.readAsDataURL(file);
35
40
  };
36
41
  input.click();
37
42
  }
38
43
  catch {
44
+ observeWebcamHostResult('capture', false);
39
45
  resolve(null);
40
46
  }
41
47
  });
@@ -43,6 +49,7 @@ export function createWebWebcamBackend() {
43
49
  captureVideo(options) {
44
50
  return new Promise((resolve) => {
45
51
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
52
+ observeWebcamHostResult('captureVideo', false);
46
53
  resolve(null);
47
54
  return;
48
55
  }
@@ -60,46 +67,80 @@ export function createWebWebcamBackend() {
60
67
  }
61
68
  const reader = new FileReader();
62
69
  reader.onload = () => {
70
+ observeWebcamHostResult('captureVideo', true);
63
71
  resolve({
64
72
  dataUrl: typeof reader.result === 'string' ? reader.result : '',
65
- // The web file input cannot decode the clip; native hosts report a real duration.
66
73
  duration: 0,
67
74
  format: file.type,
68
75
  });
69
76
  };
70
- reader.onerror = () => resolve(null);
77
+ reader.onerror = () => {
78
+ observeWebcamHostResult('captureVideo', false);
79
+ resolve(null);
80
+ };
71
81
  reader.readAsDataURL(file);
72
82
  };
73
83
  input.click();
74
84
  }
75
85
  catch {
86
+ observeWebcamHostResult('captureVideo', false);
76
87
  resolve(null);
77
88
  }
78
89
  });
79
90
  },
80
91
  async requestPermission() {
81
- // The Permissions API only reports state; it does not prompt. A native host performs a real
82
- // permission request. Returns false when the API is absent (jsdom, older browsers) or denied.
83
- if (typeof navigator === 'undefined')
92
+ if (typeof navigator === 'undefined') {
93
+ observeWebcamHostResult('requestPermission', false);
84
94
  return false;
95
+ }
85
96
  try {
86
97
  const permissions = navigator.permissions;
87
- if (permissions === undefined || typeof permissions.query !== 'function')
98
+ if (permissions === undefined || typeof permissions.query !== 'function') {
99
+ observeWebcamHostResult('requestPermission', false);
88
100
  return false;
101
+ }
89
102
  const status = await permissions.query({ name: 'camera' });
90
- return status.state === 'granted';
103
+ const granted = status.state === 'granted';
104
+ observeWebcamHostResult('requestPermission', granted);
105
+ return granted;
91
106
  }
92
107
  catch {
108
+ observeWebcamHostResult('requestPermission', false);
93
109
  return false;
94
110
  }
95
111
  },
96
112
  };
97
113
  }
98
- // The active camera backend, or a lazily-created web default. There is always a backend.
114
+ export function explainWebcamBackend() {
115
+ if (_custom !== null) {
116
+ return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
117
+ }
118
+ if (_host !== null) {
119
+ return {
120
+ conflict: _hostConflict,
121
+ layer: 'host',
122
+ operation: _hostObservation !== null ? _hostObservation.operation : null,
123
+ viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
124
+ };
125
+ }
126
+ return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
127
+ }
99
128
  export function getWebcamBackend() {
100
- if (_backend === null)
101
- _backend = createWebWebcamBackend();
102
- return _backend;
129
+ return _custom ?? _host ?? _sentinel;
130
+ }
131
+ export function installWebcamHostBackend(backend) {
132
+ if (_host !== null) {
133
+ if (_host !== backend)
134
+ _hostConflict = true;
135
+ return;
136
+ }
137
+ _host = backend;
138
+ }
139
+ export function observeWebcamHostResult(operation, succeeded) {
140
+ _hostObservation = {
141
+ operation,
142
+ viability: succeeded ? 'available' : 'runtime-api-unavailable',
143
+ };
103
144
  }
104
145
  // Records a video from the device camera. Resolves null when cancelled, denied, or unavailable.
105
146
  export function recordWebcamVideo(options) {
@@ -109,17 +150,36 @@ export function recordWebcamVideo(options) {
109
150
  export function requestWebcamPermission() {
110
151
  return getWebcamBackend().requestPermission();
111
152
  }
153
+ export function resetWebcamBackendForTest() {
154
+ _custom = null;
155
+ _host = null;
156
+ _hostConflict = false;
157
+ _hostObservation = null;
158
+ }
112
159
  // Picks an existing image from the photo library. Resolves null when cancelled or unavailable.
113
160
  export function selectWebcamImage(options) {
114
161
  return getWebcamBackend().capture({ ...options, source: 'photos' });
115
162
  }
116
- // Installs a native host camera backend; pass null to fall back to the web default.
117
163
  export function setWebcamBackend(backend) {
118
- _backend = backend;
164
+ _custom = backend;
119
165
  }
120
166
  // Captures a photo from the device camera. Resolves null when cancelled, denied, or unavailable.
121
167
  export function takeWebcamPhoto(options) {
122
168
  return getWebcamBackend().capture({ ...options, source: 'camera' });
123
169
  }
124
- let _backend = null;
170
+ let _custom = null;
171
+ let _host = null;
172
+ let _hostConflict = false;
173
+ let _hostObservation = null;
174
+ const _sentinel = {
175
+ async capture() {
176
+ return null;
177
+ },
178
+ async captureVideo() {
179
+ return null;
180
+ },
181
+ async requestPermission() {
182
+ return false;
183
+ },
184
+ };
125
185
  //# sourceMappingURL=webcam.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webcam.js","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webcam.js","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/webcam",
3
- "version": "0.4.0-next.1898.266773a",
3
+ "version": "0.4.0-next.1908.751021c",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,8 +38,8 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/entity": "0.4.0-next.1898.266773a",
42
- "@flighthq/types": "0.4.0-next.1898.266773a"
41
+ "@flighthq/entity": "0.4.0-next.1908.751021c",
42
+ "@flighthq/types": "0.4.0-next.1908.751021c"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.3.0"
@@ -8,6 +8,10 @@ import {
8
8
  selectWebcamImage,
9
9
  setWebcamBackend,
10
10
  takeWebcamPhoto,
11
+ explainWebcamBackend,
12
+ installWebcamHostBackend,
13
+ observeWebcamHostResult,
14
+ resetWebcamBackendForTest,
11
15
  } from './webcam';
12
16
 
13
17
  function fakeBackend(): WebcamBackend & { lastOptions: WebcamCaptureOptions | null } {
@@ -36,6 +40,34 @@ describe('createWebWebcamBackend', () => {
36
40
  });
37
41
  });
38
42
 
43
+ describe('explainWebcamBackend', () => {
44
+ afterEach(() => resetWebcamBackendForTest());
45
+
46
+ it('reports host-not-enabled when no backend is installed', () => {
47
+ resetWebcamBackendForTest();
48
+ const explanation = explainWebcamBackend();
49
+ expect(explanation.layer).toBe('host-not-enabled');
50
+ expect(explanation.conflict).toBe(false);
51
+ expect(explanation.viability).toBe('unobserved');
52
+ });
53
+
54
+ it('reports custom layer when a custom backend is set', () => {
55
+ setWebcamBackend(fakeBackend());
56
+ expect(explainWebcamBackend().layer).toBe('custom');
57
+ });
58
+
59
+ it('reports host layer when a host backend is installed', () => {
60
+ installWebcamHostBackend(fakeBackend());
61
+ expect(explainWebcamBackend().layer).toBe('host');
62
+ });
63
+
64
+ it('reports conflict when two different host backends are installed', () => {
65
+ installWebcamHostBackend(fakeBackend());
66
+ installWebcamHostBackend(fakeBackend());
67
+ expect(explainWebcamBackend().conflict).toBe(true);
68
+ });
69
+ });
70
+
39
71
  describe('getWebcamBackend', () => {
40
72
  it('falls back to a web backend', () => {
41
73
  expect(getWebcamBackend()).not.toBeNull();
@@ -48,6 +80,43 @@ describe('getWebcamBackend', () => {
48
80
  });
49
81
  });
50
82
 
83
+ describe('installWebcamHostBackend', () => {
84
+ afterEach(() => resetWebcamBackendForTest());
85
+
86
+ it('installs a host backend that getWebcamBackend returns', () => {
87
+ const backend = fakeBackend();
88
+ installWebcamHostBackend(backend);
89
+ expect(getWebcamBackend()).toBe(backend);
90
+ });
91
+
92
+ it('is first-host-wins: a second different backend sets conflict', () => {
93
+ const first = fakeBackend();
94
+ const second = fakeBackend();
95
+ installWebcamHostBackend(first);
96
+ installWebcamHostBackend(second);
97
+ expect(getWebcamBackend()).toBe(first);
98
+ expect(explainWebcamBackend().conflict).toBe(true);
99
+ });
100
+ });
101
+
102
+ describe('observeWebcamHostResult', () => {
103
+ afterEach(() => resetWebcamBackendForTest());
104
+
105
+ it('records a successful observation', () => {
106
+ installWebcamHostBackend(fakeBackend());
107
+ observeWebcamHostResult('capture', true);
108
+ const explanation = explainWebcamBackend();
109
+ expect(explanation.operation).toBe('capture');
110
+ expect(explanation.viability).toBe('available');
111
+ });
112
+
113
+ it('records a failed observation', () => {
114
+ installWebcamHostBackend(fakeBackend());
115
+ observeWebcamHostResult('capture', false);
116
+ expect(explainWebcamBackend().viability).toBe('runtime-api-unavailable');
117
+ });
118
+ });
119
+
51
120
  describe('recordWebcamVideo', () => {
52
121
  it('captures video with the camera source', async () => {
53
122
  const backend = fakeBackend();
@@ -74,6 +143,18 @@ describe('requestWebcamPermission', () => {
74
143
  });
75
144
  });
76
145
 
146
+ describe('resetWebcamBackendForTest', () => {
147
+ it('clears all backend slots', () => {
148
+ setWebcamBackend(fakeBackend());
149
+ installWebcamHostBackend(fakeBackend());
150
+ observeWebcamHostResult('capture', true);
151
+ resetWebcamBackendForTest();
152
+ expect(explainWebcamBackend().layer).toBe('host-not-enabled');
153
+ expect(explainWebcamBackend().conflict).toBe(false);
154
+ expect(explainWebcamBackend().viability).toBe('unobserved');
155
+ });
156
+ });
157
+
77
158
  describe('selectWebcamImage', () => {
78
159
  it('captures with the photos source', async () => {
79
160
  const backend = fakeBackend();