@flighthq/webcam 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/webcam`. The `@fligh
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/webcam@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/webcam)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/webcam@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/webcam)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
@@ -1,3 +1,2 @@
1
- export * from './webcam';
2
- export * from './webcamStream';
1
+ export * from './mediaFileCapture';
3
2
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,2 @@
1
- export * from './webcam';
2
- export * from './webcamStream';
1
+ export * from './mediaFileCapture';
3
2
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { explainWebcamBackend, createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
1
+ export { explainMediaFileCaptureBackend, recordMediaFileCaptureVideo, selectMediaFileCaptureImage, takeMediaFileCapturePhoto, } 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,oBAAoB,EACpB,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,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { explainWebcamBackend, createWebcamStreamEntity, recordWebcamVideo, requestWebcamPermission, selectWebcamImage, takeWebcamPhoto, } from './contract';
1
+ export { explainMediaFileCaptureBackend, recordMediaFileCaptureVideo, selectMediaFileCaptureImage, takeMediaFileCapturePhoto, } 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,oBAAoB,EACpB,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,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { BackendExplanation } from '@flighthq/types/contract';
2
+ import type { MediaFileCaptureBackend, MediaFileCaptureOptions, MediaFileCapturePhoto, MediaFileCaptureVideo } from '@flighthq/types/contract';
3
+ export declare function createWebMediaFileCaptureBackend(): MediaFileCaptureBackend;
4
+ export declare function explainMediaFileCaptureBackend(): BackendExplanation;
5
+ export declare function getMediaFileCaptureBackend(): MediaFileCaptureBackend;
6
+ export declare function installMediaFileCaptureHostBackend(backend: MediaFileCaptureBackend): void;
7
+ export declare function observeMediaFileCaptureHostResult(operation: string, succeeded: boolean): void;
8
+ export declare function recordMediaFileCaptureVideo(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCaptureVideo | null>;
9
+ export declare function resetMediaFileCaptureBackendForTest(): void;
10
+ export declare function selectMediaFileCaptureImage(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCapturePhoto | null>;
11
+ export declare function setMediaFileCaptureBackend(backend: MediaFileCaptureBackend | null): void;
12
+ export declare function takeMediaFileCapturePhoto(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCapturePhoto | null>;
13
+ //# sourceMappingURL=mediaFileCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaFileCapture.d.ts","sourceRoot":"","sources":["../src/mediaFileCapture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,gCAAgC,IAAI,uBAAuB,CA0F1E;AAED,wBAAgB,8BAA8B,IAAI,kBAAkB,CAanE;AAED,wBAAgB,0BAA0B,IAAI,uBAAuB,CAEpE;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAMzF;AAED,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAK7F;AAGD,wBAAgB,2BAA2B,CACzC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAK1D;AAGD,wBAAgB,2BAA2B,CACzC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAExF;AAGD,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC"}
@@ -1,12 +1,12 @@
1
1
  // Builds the default web backend over a transient <input type="file">. capture resolves to null when
2
2
  // the document is absent (jsdom), the user cancels, or the file cannot be read — capture is not
3
3
  // guaranteed. Real pixel dimensions are not decoded; width/height resolve to 0.
4
- export function createWebWebcamBackend() {
4
+ export function createWebMediaFileCaptureBackend() {
5
5
  return {
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
+ observeMediaFileCaptureHostResult('capture', false);
10
10
  resolve(null);
11
11
  return;
12
12
  }
@@ -16,32 +16,35 @@ export function createWebWebcamBackend() {
16
16
  input.accept = 'image/*';
17
17
  if (options.source === 'camera')
18
18
  input.capture = 'environment';
19
+ const settle = (value) => {
20
+ input.onchange = null;
21
+ input.oncancel = null;
22
+ observeMediaFileCaptureHostResult('capture', value !== null);
23
+ resolve(value);
24
+ };
19
25
  input.onchange = () => {
20
26
  const file = input.files?.[0] ?? null;
21
27
  if (file === null) {
22
- resolve(null);
28
+ settle(null);
23
29
  return;
24
30
  }
25
31
  const reader = new FileReader();
26
32
  reader.onload = () => {
27
- observeWebcamHostResult('capture', true);
28
- resolve({
33
+ settle({
29
34
  dataUrl: typeof reader.result === 'string' ? reader.result : '',
30
35
  width: 0,
31
36
  height: 0,
32
37
  format: file.type,
33
38
  });
34
39
  };
35
- reader.onerror = () => {
36
- observeWebcamHostResult('capture', false);
37
- resolve(null);
38
- };
40
+ reader.onerror = () => settle(null);
39
41
  reader.readAsDataURL(file);
40
42
  };
43
+ input.oncancel = () => settle(null);
41
44
  input.click();
42
45
  }
43
46
  catch {
44
- observeWebcamHostResult('capture', false);
47
+ observeMediaFileCaptureHostResult('capture', false);
45
48
  resolve(null);
46
49
  }
47
50
  });
@@ -49,7 +52,7 @@ export function createWebWebcamBackend() {
49
52
  captureVideo(options) {
50
53
  return new Promise((resolve) => {
51
54
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
52
- observeWebcamHostResult('captureVideo', false);
55
+ observeMediaFileCaptureHostResult('captureVideo', false);
53
56
  resolve(null);
54
57
  return;
55
58
  }
@@ -59,59 +62,41 @@ export function createWebWebcamBackend() {
59
62
  input.accept = 'video/*';
60
63
  if (options.source === 'camera')
61
64
  input.capture = 'environment';
65
+ const settle = (value) => {
66
+ input.onchange = null;
67
+ input.oncancel = null;
68
+ observeMediaFileCaptureHostResult('captureVideo', value !== null);
69
+ resolve(value);
70
+ };
62
71
  input.onchange = () => {
63
72
  const file = input.files?.[0] ?? null;
64
73
  if (file === null) {
65
- resolve(null);
74
+ settle(null);
66
75
  return;
67
76
  }
68
77
  const reader = new FileReader();
69
78
  reader.onload = () => {
70
- observeWebcamHostResult('captureVideo', true);
71
- resolve({
79
+ settle({
72
80
  dataUrl: typeof reader.result === 'string' ? reader.result : '',
73
81
  duration: 0,
74
82
  format: file.type,
75
83
  });
76
84
  };
77
- reader.onerror = () => {
78
- observeWebcamHostResult('captureVideo', false);
79
- resolve(null);
80
- };
85
+ reader.onerror = () => settle(null);
81
86
  reader.readAsDataURL(file);
82
87
  };
88
+ input.oncancel = () => settle(null);
83
89
  input.click();
84
90
  }
85
91
  catch {
86
- observeWebcamHostResult('captureVideo', false);
92
+ observeMediaFileCaptureHostResult('captureVideo', false);
87
93
  resolve(null);
88
94
  }
89
95
  });
90
96
  },
91
- async requestPermission() {
92
- if (typeof navigator === 'undefined') {
93
- observeWebcamHostResult('requestPermission', false);
94
- return false;
95
- }
96
- try {
97
- const permissions = navigator.permissions;
98
- if (permissions === undefined || typeof permissions.query !== 'function') {
99
- observeWebcamHostResult('requestPermission', false);
100
- return false;
101
- }
102
- const status = await permissions.query({ name: 'camera' });
103
- const granted = status.state === 'granted';
104
- observeWebcamHostResult('requestPermission', granted);
105
- return granted;
106
- }
107
- catch {
108
- observeWebcamHostResult('requestPermission', false);
109
- return false;
110
- }
111
- },
112
97
  };
113
98
  }
114
- export function explainWebcamBackend() {
99
+ export function explainMediaFileCaptureBackend() {
115
100
  if (_custom !== null) {
116
101
  return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
117
102
  }
@@ -125,10 +110,10 @@ export function explainWebcamBackend() {
125
110
  }
126
111
  return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
127
112
  }
128
- export function getWebcamBackend() {
113
+ export function getMediaFileCaptureBackend() {
129
114
  return _custom ?? _host ?? _sentinel;
130
115
  }
131
- export function installWebcamHostBackend(backend) {
116
+ export function installMediaFileCaptureHostBackend(backend) {
132
117
  if (_host !== null) {
133
118
  if (_host !== backend)
134
119
  _hostConflict = true;
@@ -136,36 +121,32 @@ export function installWebcamHostBackend(backend) {
136
121
  }
137
122
  _host = backend;
138
123
  }
139
- export function observeWebcamHostResult(operation, succeeded) {
124
+ export function observeMediaFileCaptureHostResult(operation, succeeded) {
140
125
  _hostObservation = {
141
126
  operation,
142
127
  viability: succeeded ? 'available' : 'runtime-api-unavailable',
143
128
  };
144
129
  }
145
130
  // Records a video from the device camera. Resolves null when cancelled, denied, or unavailable.
146
- export function recordWebcamVideo(options) {
147
- return getWebcamBackend().captureVideo({ ...options, source: 'camera' });
131
+ export function recordMediaFileCaptureVideo(options) {
132
+ return getMediaFileCaptureBackend().captureVideo({ ...options, source: 'camera' });
148
133
  }
149
- // Requests camera access permission. Resolves false when denied or when the host cannot prompt.
150
- export function requestWebcamPermission() {
151
- return getWebcamBackend().requestPermission();
152
- }
153
- export function resetWebcamBackendForTest() {
134
+ export function resetMediaFileCaptureBackendForTest() {
154
135
  _custom = null;
155
136
  _host = null;
156
137
  _hostConflict = false;
157
138
  _hostObservation = null;
158
139
  }
159
140
  // Picks an existing image from the photo library. Resolves null when cancelled or unavailable.
160
- export function selectWebcamImage(options) {
161
- return getWebcamBackend().capture({ ...options, source: 'photos' });
141
+ export function selectMediaFileCaptureImage(options) {
142
+ return getMediaFileCaptureBackend().capture({ ...options, source: 'photos' });
162
143
  }
163
- export function setWebcamBackend(backend) {
144
+ export function setMediaFileCaptureBackend(backend) {
164
145
  _custom = backend;
165
146
  }
166
147
  // Captures a photo from the device camera. Resolves null when cancelled, denied, or unavailable.
167
- export function takeWebcamPhoto(options) {
168
- return getWebcamBackend().capture({ ...options, source: 'camera' });
148
+ export function takeMediaFileCapturePhoto(options) {
149
+ return getMediaFileCaptureBackend().capture({ ...options, source: 'camera' });
169
150
  }
170
151
  let _custom = null;
171
152
  let _host = null;
@@ -178,8 +159,5 @@ const _sentinel = {
178
159
  async captureVideo() {
179
160
  return null;
180
161
  },
181
- async requestPermission() {
182
- return false;
183
- },
184
162
  };
185
- //# sourceMappingURL=webcam.js.map
163
+ //# sourceMappingURL=mediaFileCapture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaFileCapture.js","sourceRoot":"","sources":["../src/mediaFileCapture.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.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
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.5.0-next.1944.cd6b6e7",
42
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/entity": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.3.0"
@@ -0,0 +1,207 @@
1
+ import type { MediaFileCaptureBackend, MediaFileCaptureOptions } from '@flighthq/types/contract';
2
+
3
+ import {
4
+ createWebMediaFileCaptureBackend,
5
+ explainMediaFileCaptureBackend,
6
+ getMediaFileCaptureBackend,
7
+ installMediaFileCaptureHostBackend,
8
+ observeMediaFileCaptureHostResult,
9
+ recordMediaFileCaptureVideo,
10
+ resetMediaFileCaptureBackendForTest,
11
+ selectMediaFileCaptureImage,
12
+ setMediaFileCaptureBackend,
13
+ takeMediaFileCapturePhoto,
14
+ } from './mediaFileCapture';
15
+
16
+ function fakeBackend(): MediaFileCaptureBackend & { lastOptions: MediaFileCaptureOptions | null } {
17
+ return {
18
+ lastOptions: null,
19
+ async capture(options) {
20
+ this.lastOptions = { ...options };
21
+ return { dataUrl: 'data:image/png;base64,xx', width: 0, height: 0, format: 'image/png' };
22
+ },
23
+ async captureVideo(options) {
24
+ this.lastOptions = { ...options };
25
+ return { dataUrl: 'data:video/mp4;base64,xx', duration: 0, format: 'video/mp4' };
26
+ },
27
+ };
28
+ }
29
+
30
+ afterEach(() => setMediaFileCaptureBackend(null));
31
+
32
+ describe('createWebMediaFileCaptureBackend', () => {
33
+ afterEach(() => vi.restoreAllMocks());
34
+
35
+ for (const method of ['capture', 'captureVideo'] as const) {
36
+ it(`${method} settles with null and detaches handlers when the picker is dismissed`, async () => {
37
+ const created: HTMLInputElement[] = [];
38
+ const createElement = document.createElement.bind(document);
39
+ vi.spyOn(document, 'createElement').mockImplementation(((tag: string) => {
40
+ const element = createElement(tag);
41
+ if (tag === 'input') created.push(element as HTMLInputElement);
42
+ return element;
43
+ }) as typeof document.createElement);
44
+
45
+ const pending = createWebMediaFileCaptureBackend()[method]({ source: 'photos' });
46
+ created[0].dispatchEvent(new Event('cancel'));
47
+
48
+ await expect(
49
+ Promise.race([pending, new Promise<'pending'>((resolve) => setTimeout(() => resolve('pending'), 200))]),
50
+ ).resolves.toBeNull();
51
+ expect(created[0].onchange).toBeNull();
52
+ expect(created[0].oncancel).toBeNull();
53
+ });
54
+ }
55
+
56
+ it('returns a backend whose capture yields a Promise without throwing synchronously', () => {
57
+ const backend = createWebMediaFileCaptureBackend();
58
+ expect(backend.capture({}) instanceof Promise).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe('explainMediaFileCaptureBackend', () => {
63
+ afterEach(() => resetMediaFileCaptureBackendForTest());
64
+
65
+ it('reports host-not-enabled when no backend is installed', () => {
66
+ resetMediaFileCaptureBackendForTest();
67
+ const explanation = explainMediaFileCaptureBackend();
68
+ expect(explanation.layer).toBe('host-not-enabled');
69
+ expect(explanation.conflict).toBe(false);
70
+ expect(explanation.viability).toBe('unobserved');
71
+ });
72
+
73
+ it('reports custom layer when a custom backend is set', () => {
74
+ setMediaFileCaptureBackend(fakeBackend());
75
+ expect(explainMediaFileCaptureBackend().layer).toBe('custom');
76
+ });
77
+
78
+ it('reports host layer when a host backend is installed', () => {
79
+ installMediaFileCaptureHostBackend(fakeBackend());
80
+ expect(explainMediaFileCaptureBackend().layer).toBe('host');
81
+ });
82
+
83
+ it('reports conflict when two different host backends are installed', () => {
84
+ installMediaFileCaptureHostBackend(fakeBackend());
85
+ installMediaFileCaptureHostBackend(fakeBackend());
86
+ expect(explainMediaFileCaptureBackend().conflict).toBe(true);
87
+ });
88
+ });
89
+
90
+ describe('getMediaFileCaptureBackend', () => {
91
+ it('falls back to a web backend', () => {
92
+ expect(getMediaFileCaptureBackend()).not.toBeNull();
93
+ });
94
+
95
+ it('returns the registered backend', () => {
96
+ const backend = fakeBackend();
97
+ setMediaFileCaptureBackend(backend);
98
+ expect(getMediaFileCaptureBackend()).toBe(backend);
99
+ });
100
+ });
101
+
102
+ describe('installMediaFileCaptureHostBackend', () => {
103
+ afterEach(() => resetMediaFileCaptureBackendForTest());
104
+
105
+ it('installs a host backend that getMediaFileCaptureBackend returns', () => {
106
+ const backend = fakeBackend();
107
+ installMediaFileCaptureHostBackend(backend);
108
+ expect(getMediaFileCaptureBackend()).toBe(backend);
109
+ });
110
+
111
+ it('is first-host-wins: a second different backend sets conflict', () => {
112
+ const first = fakeBackend();
113
+ const second = fakeBackend();
114
+ installMediaFileCaptureHostBackend(first);
115
+ installMediaFileCaptureHostBackend(second);
116
+ expect(getMediaFileCaptureBackend()).toBe(first);
117
+ expect(explainMediaFileCaptureBackend().conflict).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe('mediaFileCapture public API boundary', () => {
122
+ it('web backend has exactly capture and captureVideo methods', () => {
123
+ const backend = createWebMediaFileCaptureBackend();
124
+ const methods = Object.keys(backend).sort();
125
+ expect(methods).toEqual(['capture', 'captureVideo']);
126
+ });
127
+
128
+ it('sentinel backend has no requestPermission method', () => {
129
+ resetMediaFileCaptureBackendForTest();
130
+ setMediaFileCaptureBackend(null);
131
+ const sentinel = getMediaFileCaptureBackend();
132
+ expect('requestPermission' in sentinel).toBe(false);
133
+ });
134
+ });
135
+
136
+ describe('observeMediaFileCaptureHostResult', () => {
137
+ afterEach(() => resetMediaFileCaptureBackendForTest());
138
+
139
+ it('records a successful observation', () => {
140
+ installMediaFileCaptureHostBackend(fakeBackend());
141
+ observeMediaFileCaptureHostResult('capture', true);
142
+ const explanation = explainMediaFileCaptureBackend();
143
+ expect(explanation.operation).toBe('capture');
144
+ expect(explanation.viability).toBe('available');
145
+ });
146
+
147
+ it('records a failed observation', () => {
148
+ installMediaFileCaptureHostBackend(fakeBackend());
149
+ observeMediaFileCaptureHostResult('capture', false);
150
+ expect(explainMediaFileCaptureBackend().viability).toBe('runtime-api-unavailable');
151
+ });
152
+ });
153
+
154
+ describe('recordMediaFileCaptureVideo', () => {
155
+ it('captures video with the camera source', async () => {
156
+ const backend = fakeBackend();
157
+ setMediaFileCaptureBackend(backend);
158
+ const video = await recordMediaFileCaptureVideo({ maxDurationMs: 5000 });
159
+ expect(video).not.toBeNull();
160
+ expect(backend.lastOptions).toEqual({ maxDurationMs: 5000, source: 'camera' });
161
+ });
162
+
163
+ it('returns a Promise from the web backend without throwing', () => {
164
+ const backend = createWebMediaFileCaptureBackend();
165
+ expect(backend.captureVideo({}) instanceof Promise).toBe(true);
166
+ });
167
+ });
168
+
169
+ describe('resetMediaFileCaptureBackendForTest', () => {
170
+ it('clears all backend slots', () => {
171
+ setMediaFileCaptureBackend(fakeBackend());
172
+ installMediaFileCaptureHostBackend(fakeBackend());
173
+ observeMediaFileCaptureHostResult('capture', true);
174
+ resetMediaFileCaptureBackendForTest();
175
+ expect(explainMediaFileCaptureBackend().layer).toBe('host-not-enabled');
176
+ expect(explainMediaFileCaptureBackend().conflict).toBe(false);
177
+ expect(explainMediaFileCaptureBackend().viability).toBe('unobserved');
178
+ });
179
+ });
180
+
181
+ describe('selectMediaFileCaptureImage', () => {
182
+ it('captures with the photos source', async () => {
183
+ const backend = fakeBackend();
184
+ setMediaFileCaptureBackend(backend);
185
+ const photo = await selectMediaFileCaptureImage({ quality: 0.5 });
186
+ expect(photo).not.toBeNull();
187
+ expect(backend.lastOptions).toEqual({ quality: 0.5, source: 'photos' });
188
+ });
189
+ });
190
+
191
+ describe('setMediaFileCaptureBackend', () => {
192
+ it('clears back to the web fallback when passed null', () => {
193
+ setMediaFileCaptureBackend(fakeBackend());
194
+ setMediaFileCaptureBackend(null);
195
+ expect(getMediaFileCaptureBackend()).not.toBeNull();
196
+ });
197
+ });
198
+
199
+ describe('takeMediaFileCapturePhoto', () => {
200
+ it('captures with the camera source', async () => {
201
+ const backend = fakeBackend();
202
+ setMediaFileCaptureBackend(backend);
203
+ const photo = await takeMediaFileCapturePhoto({ allowEditing: true });
204
+ expect(photo).not.toBeNull();
205
+ expect(backend.lastOptions).toEqual({ allowEditing: true, source: 'camera' });
206
+ });
207
+ });
package/dist/webcam.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import type { BackendExplanation } from '@flighthq/types/contract';
2
- import type { WebcamBackend, WebcamCaptureOptions, WebcamPhoto, WebcamVideo } from '@flighthq/types/contract';
3
- export declare function createWebWebcamBackend(): WebcamBackend;
4
- export declare function explainWebcamBackend(): BackendExplanation;
5
- export declare function getWebcamBackend(): WebcamBackend;
6
- export declare function installWebcamHostBackend(backend: WebcamBackend): void;
7
- export declare function observeWebcamHostResult(operation: string, succeeded: boolean): void;
8
- export declare function recordWebcamVideo(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamVideo | null>;
9
- export declare function requestWebcamPermission(): Promise<boolean>;
10
- export declare function resetWebcamBackendForTest(): void;
11
- export declare function selectWebcamImage(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
12
- export declare function setWebcamBackend(backend: WebcamBackend | null): void;
13
- export declare function takeWebcamPhoto(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
14
- //# sourceMappingURL=webcam.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webcam.js","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"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"}
@@ -1,12 +0,0 @@
1
- import type { WebcamFacingMode, WebcamStream, WebcamStreamRuntime } from '@flighthq/types/contract';
2
- export declare function createWebcamStreamEntity(data: {
3
- active: boolean;
4
- deviceId: string;
5
- facingMode: WebcamFacingMode | null;
6
- frameRate: number;
7
- height: number;
8
- id: string;
9
- width: number;
10
- }): WebcamStream;
11
- export declare function getWebcamStreamRuntime(stream: Readonly<WebcamStream>): WebcamStreamRuntime | null;
12
- //# sourceMappingURL=webcamStream.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webcamStream.d.ts","sourceRoot":"","sources":["../src/webcamStream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAMpG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,YAAY,CAoBf;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAIjG"}
@@ -1,34 +0,0 @@
1
- import { createEntity } from '@flighthq/entity/contract';
2
- import { EntityRuntimeKey } from '@flighthq/types/contract';
3
- // Allocates a WebcamStream entity with an attached runtime slot holding the MediaStream.
4
- // The fields in data become the entity's public fields; the runtime is initialized with a
5
- // placeholder MediaStream that is replaced immediately by the caller.
6
- export function createWebcamStreamEntity(data) {
7
- const stream = createEntity({
8
- active: data.active,
9
- deviceId: data.deviceId,
10
- facingMode: data.facingMode,
11
- frameRate: data.frameRate,
12
- height: data.height,
13
- id: data.id,
14
- width: data.width,
15
- });
16
- // Initialize with null; the caller overwrites rt.mediaStream once a live stream is acquired. Using
17
- // null (not new MediaStream()) avoids depending on MediaStream in environments that lack it (jsdom
18
- // without the media API).
19
- const rt = {
20
- binding: null,
21
- mediaStream: null,
22
- videoElement: null,
23
- };
24
- stream[EntityRuntimeKey] = rt;
25
- return stream;
26
- }
27
- // Returns the WebcamStreamRuntime attached to stream, or null when stream has no runtime.
28
- export function getWebcamStreamRuntime(stream) {
29
- const rt = stream[EntityRuntimeKey];
30
- if (rt === undefined || rt === null)
31
- return null;
32
- return rt;
33
- }
34
- //# sourceMappingURL=webcamStream.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webcamStream.js","sourceRoot":"","sources":["../src/webcamStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,yFAAyF;AACzF,0FAA0F;AAC1F,sEAAsE;AACtE,MAAM,UAAU,wBAAwB,CAAC,IAQxC;IACC,MAAM,MAAM,GAAG,YAAY,CAAC;QAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IACH,mGAAmG;IACnG,mGAAmG;IACnG,0BAA0B;IAC1B,MAAM,EAAE,GAAwB;QAC9B,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,IAAI;KACnB,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,MAA8B;IACnE,MAAM,EAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACpC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,EAAyB,CAAC;AACnC,CAAC"}
@@ -1,184 +0,0 @@
1
- import type { WebcamBackend, WebcamCaptureOptions } from '@flighthq/types/contract';
2
-
3
- import {
4
- createWebWebcamBackend,
5
- getWebcamBackend,
6
- recordWebcamVideo,
7
- requestWebcamPermission,
8
- selectWebcamImage,
9
- setWebcamBackend,
10
- takeWebcamPhoto,
11
- explainWebcamBackend,
12
- installWebcamHostBackend,
13
- observeWebcamHostResult,
14
- resetWebcamBackendForTest,
15
- } from './webcam';
16
-
17
- function fakeBackend(): WebcamBackend & { lastOptions: WebcamCaptureOptions | null } {
18
- return {
19
- lastOptions: null,
20
- async capture(options) {
21
- this.lastOptions = { ...options };
22
- return { dataUrl: 'data:image/png;base64,xx', width: 0, height: 0, format: 'image/png' };
23
- },
24
- async captureVideo(options) {
25
- this.lastOptions = { ...options };
26
- return { dataUrl: 'data:video/mp4;base64,xx', duration: 0, format: 'video/mp4' };
27
- },
28
- async requestPermission() {
29
- return true;
30
- },
31
- };
32
- }
33
-
34
- afterEach(() => setWebcamBackend(null));
35
-
36
- describe('createWebWebcamBackend', () => {
37
- it('returns a backend whose capture yields a Promise without throwing synchronously', () => {
38
- const backend = createWebWebcamBackend();
39
- expect(backend.capture({}) instanceof Promise).toBe(true);
40
- });
41
- });
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
-
71
- describe('getWebcamBackend', () => {
72
- it('falls back to a web backend', () => {
73
- expect(getWebcamBackend()).not.toBeNull();
74
- });
75
-
76
- it('returns the registered backend', () => {
77
- const backend = fakeBackend();
78
- setWebcamBackend(backend);
79
- expect(getWebcamBackend()).toBe(backend);
80
- });
81
- });
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
-
120
- describe('recordWebcamVideo', () => {
121
- it('captures video with the camera source', async () => {
122
- const backend = fakeBackend();
123
- setWebcamBackend(backend);
124
- const video = await recordWebcamVideo({ maxDurationMs: 5000 });
125
- expect(video).not.toBeNull();
126
- expect(backend.lastOptions).toEqual({ maxDurationMs: 5000, source: 'camera' });
127
- });
128
-
129
- it('returns a Promise from the web backend without throwing', () => {
130
- const backend = createWebWebcamBackend();
131
- expect(backend.captureVideo({}) instanceof Promise).toBe(true);
132
- });
133
- });
134
-
135
- describe('requestWebcamPermission', () => {
136
- it('delegates to the active backend', async () => {
137
- setWebcamBackend(fakeBackend());
138
- expect(await requestWebcamPermission()).toBe(true);
139
- });
140
-
141
- it('returns a boolean from the web backend without throwing', async () => {
142
- expect(typeof (await requestWebcamPermission())).toBe('boolean');
143
- });
144
- });
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
-
158
- describe('selectWebcamImage', () => {
159
- it('captures with the photos source', async () => {
160
- const backend = fakeBackend();
161
- setWebcamBackend(backend);
162
- const photo = await selectWebcamImage({ quality: 0.5 });
163
- expect(photo).not.toBeNull();
164
- expect(backend.lastOptions).toEqual({ quality: 0.5, source: 'photos' });
165
- });
166
- });
167
-
168
- describe('setWebcamBackend', () => {
169
- it('clears back to the web fallback when passed null', () => {
170
- setWebcamBackend(fakeBackend());
171
- setWebcamBackend(null);
172
- expect(getWebcamBackend()).not.toBeNull();
173
- });
174
- });
175
-
176
- describe('takeWebcamPhoto', () => {
177
- it('captures with the camera source', async () => {
178
- const backend = fakeBackend();
179
- setWebcamBackend(backend);
180
- const photo = await takeWebcamPhoto({ allowEditing: true });
181
- expect(photo).not.toBeNull();
182
- expect(backend.lastOptions).toEqual({ allowEditing: true, source: 'camera' });
183
- });
184
- });
@@ -1,86 +0,0 @@
1
- import type { WebcamStream } from '@flighthq/types/contract';
2
- import { EntityRuntimeKey } from '@flighthq/types/contract';
3
-
4
- import { createWebcamStreamEntity, getWebcamStreamRuntime } from './webcamStream';
5
-
6
- describe('createWebcamStreamEntity', () => {
7
- it('creates a WebcamStream with the correct public fields', () => {
8
- const stream = createWebcamStreamEntity({
9
- active: true,
10
- deviceId: 'device-1',
11
- facingMode: 'environment',
12
- frameRate: 30,
13
- height: 480,
14
- id: 'stream-123',
15
- width: 640,
16
- });
17
- expect(stream.active).toBe(true);
18
- expect(stream.deviceId).toBe('device-1');
19
- expect(stream.facingMode).toBe('environment');
20
- expect(stream.frameRate).toBe(30);
21
- expect(stream.height).toBe(480);
22
- expect(stream.id).toBe('stream-123');
23
- expect(stream.width).toBe(640);
24
- });
25
-
26
- it('attaches a runtime slot to the entity', () => {
27
- const stream = createWebcamStreamEntity({
28
- active: true,
29
- deviceId: '',
30
- facingMode: null,
31
- frameRate: 0,
32
- height: 0,
33
- id: 'x',
34
- width: 0,
35
- });
36
- expect(stream[EntityRuntimeKey]).not.toBeUndefined();
37
- });
38
-
39
- it('runtime slot is initialized (mediaStream placeholder is present)', () => {
40
- const stream = createWebcamStreamEntity({
41
- active: false,
42
- deviceId: '',
43
- facingMode: null,
44
- frameRate: 0,
45
- height: 0,
46
- id: 'x',
47
- width: 0,
48
- });
49
- const rt = getWebcamStreamRuntime(stream);
50
- expect(rt).not.toBeNull();
51
- // mediaStream starts null; the caller attaches a live stream before use.
52
- expect(rt?.mediaStream).toBeNull();
53
- expect(rt?.videoElement).toBeNull();
54
- });
55
- });
56
-
57
- describe('getWebcamStreamRuntime', () => {
58
- it('returns null for a stream without a runtime', () => {
59
- const stream = {
60
- active: true,
61
- deviceId: '',
62
- facingMode: null,
63
- frameRate: 0,
64
- height: 0,
65
- id: 'x',
66
- width: 0,
67
- [EntityRuntimeKey]: undefined,
68
- } as WebcamStream;
69
- expect(getWebcamStreamRuntime(stream)).toBeNull();
70
- });
71
-
72
- it('returns the runtime for a stream created by createWebcamStreamEntity', () => {
73
- const stream = createWebcamStreamEntity({
74
- active: true,
75
- deviceId: 'dev',
76
- facingMode: 'user',
77
- frameRate: 60,
78
- height: 720,
79
- id: 'stream-rt',
80
- width: 1280,
81
- });
82
- const rt = getWebcamStreamRuntime(stream);
83
- expect(rt).not.toBeNull();
84
- expect(rt?.videoElement).toBeNull();
85
- });
86
- });