artarch-feedback-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/abort.d.ts +4 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +35 -0
- package/dist/abort.js.map +1 -0
- package/dist/capture.d.ts +14 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +256 -0
- package/dist/capture.js.map +1 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +28 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tool-canvas contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# artarch-feedback-sdk
|
|
2
|
+
|
|
3
|
+
Headless browser SDK for collecting feedback with screenshots and screen recordings. The SDK owns browser capture and asset lifecycle; the host application owns authentication, CDN storage, and feedback APIs.
|
|
4
|
+
|
|
5
|
+
## About ArtArch
|
|
6
|
+
|
|
7
|
+
[ArtArch](https://www.artarch.ai/) is an AI-native Creative OS for creators and
|
|
8
|
+
brands. It brings together AI-powered content creation, reusable production
|
|
9
|
+
workflows, and leading AI models to help users create, express, grow, and monetize
|
|
10
|
+
their work.
|
|
11
|
+
|
|
12
|
+
## Purpose and capabilities
|
|
13
|
+
|
|
14
|
+
The package lets users send the visual context of a problem along with their
|
|
15
|
+
description. Capture and submission are separate operations: creating a screenshot
|
|
16
|
+
or recording returns a local `Blob`; calling `submit()` invokes the host's upload
|
|
17
|
+
and feedback adapters.
|
|
18
|
+
|
|
19
|
+
| Capability | Current behavior |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Screenshot | Captures a frame from a user-selected browser tab, window, or screen as PNG. Optional maximum dimensions preserve the source aspect ratio. |
|
|
22
|
+
| Screen recording | Records the selected surface using `MediaRecorder`, with MIME detection, manual stop, a duration limit, and browser-ended capture handling. |
|
|
23
|
+
| Shared audio | Requests audio from the selected surface with `audio: true`, subject to browser and OS support. This does not request microphone access. |
|
|
24
|
+
| Capture lifecycle | Handles permission denial, cancellation, overlapping starts/stops, final media chunks, and release of tracks and listeners. |
|
|
25
|
+
| Feedback submission | Accepts text alone, text with a screenshot, text with a recording, or both attachments. Uploads finish before the feedback record is submitted. |
|
|
26
|
+
| Business context | Forwards host-provided `context`, such as platform, project ID, route, or node ID, without collecting it automatically. |
|
|
27
|
+
|
|
28
|
+
Capture requires a secure context (HTTPS or localhost), a user gesture, and the
|
|
29
|
+
browser's permission picker. The available surfaces and audio sources depend on
|
|
30
|
+
the browser and OS. This is real screen capture, not DOM session replay.
|
|
31
|
+
|
|
32
|
+
## SDK and host responsibilities
|
|
33
|
+
|
|
34
|
+
The core is framework-independent and has no runtime dependencies on React,
|
|
35
|
+
Next.js, Axios, Sentry, or a cloud storage provider.
|
|
36
|
+
|
|
37
|
+
| Owner | Responsibilities |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| SDK | Browser capture, local media files, capture errors and cancellation, and sequencing upload/submission through `FeedbackTransport`. |
|
|
40
|
+
| Host `uploadAsset` adapter | Authentication, upload credentials, storage/CDN selection, and uploading the provided file. Returns `{ url, id? }`. |
|
|
41
|
+
| Host `submitFeedback` adapter | Sending the message, uploaded attachment URLs, and context to a business API. Returns `{ id? }`. |
|
|
42
|
+
| Host application | Feedback button and form, previews, validation, user-facing error messages, context selection, retention, and the feedback management backend. |
|
|
43
|
+
|
|
44
|
+
A host can build this user flow around the SDK:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Open feedback form -> Enter description -> Capture screenshot/recording
|
|
48
|
+
-> Preview in the host UI -> Submit -> Upload files -> Save feedback record
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`uploadAsset` can target OSS, S3, R2, or any other storage system; `submitFeedback`
|
|
52
|
+
can target an internal API or an adapter to another service. These are extension
|
|
53
|
+
interfaces, not bundled provider integrations. The host also owns cleanup of any
|
|
54
|
+
uploaded files left behind when another upload or the final submission fails.
|
|
55
|
+
|
|
56
|
+
## Current scope
|
|
57
|
+
|
|
58
|
+
The current deliverable is the core SDK. It does not include a feedback widget,
|
|
59
|
+
preview UI, screenshot annotation/redaction, video editing, a feedback backend,
|
|
60
|
+
or an administration console. It does not automatically collect console logs,
|
|
61
|
+
network requests, page URLs, or historical session replays.
|
|
62
|
+
|
|
63
|
+
The npm package name is `artarch-feedback-sdk`; its directory in this repository
|
|
64
|
+
is `packages/feedback-sdk`. The SDK is not yet connected to the production
|
|
65
|
+
`tool-canvas` feedback UI. Update this status when integrating the package.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install artarch-feedback-sdk
|
|
71
|
+
# or
|
|
72
|
+
pnpm add artarch-feedback-sdk
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For local development, run `npm pack` in this package and install the
|
|
76
|
+
resulting `.tgz` in a consuming project. See the build and test commands below.
|
|
77
|
+
|
|
78
|
+
## Host-provided transport
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { FeedbackClient } from 'artarch-feedback-sdk';
|
|
82
|
+
|
|
83
|
+
const feedback = new FeedbackClient({
|
|
84
|
+
async uploadAsset({ asset, signal }) {
|
|
85
|
+
const ticket = await getUploadTicket({
|
|
86
|
+
filename: asset.filename,
|
|
87
|
+
contentType: asset.contentType,
|
|
88
|
+
signal,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const response = await fetch(ticket.uploadUrl, {
|
|
92
|
+
method: 'PUT',
|
|
93
|
+
body: asset.blob,
|
|
94
|
+
headers: { 'Content-Type': asset.contentType },
|
|
95
|
+
signal,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) throw new Error('Asset upload failed');
|
|
99
|
+
|
|
100
|
+
return { url: ticket.cdnUrl, id: ticket.assetId };
|
|
101
|
+
},
|
|
102
|
+
submitFeedback(payload, options) {
|
|
103
|
+
return api.post('/feedback', payload, { signal: options?.signal });
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Capture and submit
|
|
109
|
+
|
|
110
|
+
`getDisplayMedia()` must be called from a user gesture. The browser will show its own permission picker every time.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const screenshot = await feedback.captureScreenshot({ preferCurrentTab: true });
|
|
114
|
+
|
|
115
|
+
await feedback.submit({
|
|
116
|
+
message: '节点预览区域显示不完整',
|
|
117
|
+
screenshot,
|
|
118
|
+
context: {
|
|
119
|
+
platform: 'studio',
|
|
120
|
+
projectId,
|
|
121
|
+
route: window.location.href,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For recording:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await feedback.startRecording({ audio: true, maxDurationMs: 60_000 });
|
|
130
|
+
// User clicks stop later.
|
|
131
|
+
const recording = await feedback.stopRecording();
|
|
132
|
+
await feedback.submit({ message, recording });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
To handle automatic completion (duration limit or the browser ending sharing),
|
|
136
|
+
await `feedback.recorder.finished` after `startRecording()` resolves. `stop()` and
|
|
137
|
+
`finished` return the same result, retained until the next `start()`. Concurrent
|
|
138
|
+
stop calls include the final media chunk. Starting while permission is pending,
|
|
139
|
+
recording is active, or the result is being finalized rejects with
|
|
140
|
+
`recording-already-started`.
|
|
141
|
+
|
|
142
|
+
Passing an `AbortSignal` cancels capture and discards the recording; use `stop()`
|
|
143
|
+
to keep it. Cancelling a pending permission prompt cannot close the browser UI,
|
|
144
|
+
but the SDK rejects promptly and releases a stream if permission is granted later.
|
|
145
|
+
Screenshots use the selected source's pixels by default. Optional width/height
|
|
146
|
+
are maximum bounds and preserve its aspect ratio.
|
|
147
|
+
|
|
148
|
+
Upload and submit errors propagate to the host. The SDK never submits feedback
|
|
149
|
+
after an upload fails or the operation is cancelled. Transport adapters must
|
|
150
|
+
honor `signal` to abort their actual HTTP requests. Cancellation cannot roll back
|
|
151
|
+
an upload or feedback record already accepted by a server; the host owns cleanup.
|
|
152
|
+
|
|
153
|
+
The package intentionally has no React dependency and no CDN client. `FeedbackTransport` is the integration boundary for a presigned upload API, S3/R2/OSS, Sentry, or an internal feedback service.
|
|
154
|
+
|
|
155
|
+
## Build and publish
|
|
156
|
+
|
|
157
|
+
Run inside this package using the npm account authorized to publish it:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
cd packages/feedback-sdk
|
|
161
|
+
npm run build
|
|
162
|
+
npm pack
|
|
163
|
+
npm publish --access public
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Tests
|
|
167
|
+
|
|
168
|
+
From the workspace root:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pnpm install --frozen-lockfile
|
|
172
|
+
pnpm --filter artarch-feedback-sdk exec playwright install chromium
|
|
173
|
+
pnpm --filter artarch-feedback-sdk test:all
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Or, after installing this package's development dependencies, run inside this directory:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm test # Node tests plus an isolated tarball consumer
|
|
180
|
+
npm run test:browser # Real Chromium capture and playback
|
|
181
|
+
npm run test:all # Both suites
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- Node tests cover permission failures, cancellation before/during capture,
|
|
185
|
+
late permission results, first-frame readiness, dimensions, recorder errors,
|
|
186
|
+
concurrent starts/stops, automatic stops, cleanup, upload ordering and failure.
|
|
187
|
+
- The package test installs the actual npm tarball in a temporary consumer
|
|
188
|
+
outside the repository and checks native Node ESM imports and NodeNext types.
|
|
189
|
+
- Playwright serves a local fixture on a random port and closes it after testing.
|
|
190
|
+
It uses native `getDisplayMedia` and `MediaRecorder`, with Chromium test flags
|
|
191
|
+
selecting/rejecting the fixture tab. Tests decode PNG pixels at desktop and
|
|
192
|
+
narrow viewport sizes, play and sample changing video frames, and check cleanup.
|
|
193
|
+
No production accounts, CDN endpoints or feedback services are contacted.
|
|
194
|
+
|
|
195
|
+
Successful screenshots and recordings are attached under `test-results/`.
|
|
196
|
+
Failed browser tests retain screenshots and traces. Browser binaries are test
|
|
197
|
+
prerequisites; on Linux CI install them with `playwright install --with-deps chromium`.
|
|
198
|
+
`prepublishOnly` runs both suites and `prepack` rebuilds the package.
|
|
199
|
+
|
|
200
|
+
Automated tests cover Chromium tab capture. They do not physically click the
|
|
201
|
+
browser toolbar's Stop Sharing button: unit tests dispatch the track's `ended`
|
|
202
|
+
event and browser tests terminate the actual stream. Before release, manually
|
|
203
|
+
check that toolbar action, the native picker, window/screen capture, and audio
|
|
204
|
+
sharing on each supported browser/OS. A narrow Chromium viewport is not mobile
|
|
205
|
+
Safari coverage.
|
package/dist/abort.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function abortError(): DOMException;
|
|
2
|
+
export declare function throwIfAborted(signal?: AbortSignal): void;
|
|
3
|
+
export declare function abortable<T>(pending: Promise<T>, signal?: AbortSignal, releaseLateResult?: (value: T) => void): Promise<T>;
|
|
4
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAEzD;AAID,wBAAgB,SAAS,CAAC,CAAC,EACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,CAAC,EAAE,WAAW,EACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GACrC,OAAO,CAAC,CAAC,CAAC,CAoBZ"}
|
package/dist/abort.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function abortError() {
|
|
2
|
+
return new DOMException('The operation was aborted.', 'AbortError');
|
|
3
|
+
}
|
|
4
|
+
export function throwIfAborted(signal) {
|
|
5
|
+
if (signal?.aborted)
|
|
6
|
+
throw abortError();
|
|
7
|
+
}
|
|
8
|
+
// Native capture prompts cannot be dismissed programmatically. Release any
|
|
9
|
+
// stream granted after cancellation, even after the caller has moved on.
|
|
10
|
+
export function abortable(pending, signal, releaseLateResult) {
|
|
11
|
+
if (!signal)
|
|
12
|
+
return pending;
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
const onAbort = () => {
|
|
16
|
+
cancelled = true;
|
|
17
|
+
signal.removeEventListener('abort', onAbort);
|
|
18
|
+
reject(abortError());
|
|
19
|
+
};
|
|
20
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
21
|
+
if (signal.aborted)
|
|
22
|
+
onAbort();
|
|
23
|
+
pending.then(value => {
|
|
24
|
+
signal.removeEventListener('abort', onAbort);
|
|
25
|
+
if (cancelled)
|
|
26
|
+
releaseLateResult?.(value);
|
|
27
|
+
else
|
|
28
|
+
resolve(value);
|
|
29
|
+
}, error => {
|
|
30
|
+
signal.removeEventListener('abort', onAbort);
|
|
31
|
+
reject(error);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,YAAY,CAAC,4BAA4B,EAAE,YAAY,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,UAAU,EAAE,CAAC;AAC1C,CAAC;AAED,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,UAAU,SAAS,CACvB,OAAmB,EACnB,MAAoB,EACpB,iBAAsC;IAEtC,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAC5B,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;YACnB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,IAAI,SAAS;gBAAE,iBAAiB,EAAE,CAAC,KAAK,CAAC,CAAC;;gBACrC,OAAO,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,EAAE,KAAK,CAAC,EAAE;YACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CapturedAsset, RecordingOptions, ScreenshotOptions } from './types.js';
|
|
2
|
+
export declare function captureScreenshot(options?: ScreenshotOptions): Promise<CapturedAsset>;
|
|
3
|
+
export declare class ScreenRecorder {
|
|
4
|
+
private phase;
|
|
5
|
+
private recorder?;
|
|
6
|
+
private completion?;
|
|
7
|
+
private requestStop?;
|
|
8
|
+
start(options?: RecordingOptions): Promise<void>;
|
|
9
|
+
/** Resolves on manual, duration-limit, or browser stop; rejects on abort/error. */
|
|
10
|
+
get finished(): Promise<CapturedAsset>;
|
|
11
|
+
stop(): Promise<CapturedAsset>;
|
|
12
|
+
get isRecording(): boolean;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAmDrF,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwC/F;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA4D;IACzE,OAAO,CAAC,QAAQ,CAAC,CAAgB;IACjC,OAAO,CAAC,UAAU,CAAC,CAAyB;IAC5C,OAAO,CAAC,WAAW,CAAC,CAAa;IAE3B,KAAK,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8G1D,mFAAmF;IACnF,IAAI,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAErC;IAED,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC;IAK9B,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF"}
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { abortable, abortError, throwIfAborted } from './abort.js';
|
|
2
|
+
import { FeedbackCaptureError, normalizeCaptureError } from './errors.js';
|
|
3
|
+
const MIME_TYPES = [
|
|
4
|
+
'video/mp4;codecs=avc1.424028,mp4a.40.2',
|
|
5
|
+
'video/mp4',
|
|
6
|
+
'video/webm;codecs=vp9,opus',
|
|
7
|
+
'video/webm;codecs=vp8,opus',
|
|
8
|
+
'video/webm',
|
|
9
|
+
];
|
|
10
|
+
function ensureBrowserSupport() {
|
|
11
|
+
if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getDisplayMedia) {
|
|
12
|
+
throw new FeedbackCaptureError('unsupported', 'Screen capture is not supported in this browser.');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function stopTracks(stream) {
|
|
16
|
+
stream.getTracks().forEach(track => track.stop());
|
|
17
|
+
}
|
|
18
|
+
function waitForFrame(video, stream, signal) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const track = stream.getVideoTracks()[0];
|
|
21
|
+
let playing = false;
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
video.removeEventListener('loadeddata', onFrame);
|
|
25
|
+
video.removeEventListener('error', onError);
|
|
26
|
+
track?.removeEventListener('ended', onError);
|
|
27
|
+
signal?.removeEventListener('abort', onAbort);
|
|
28
|
+
};
|
|
29
|
+
const fail = (error) => { cleanup(); reject(error); };
|
|
30
|
+
const onFrame = () => {
|
|
31
|
+
if (playing && video.readyState >= 2) {
|
|
32
|
+
cleanup();
|
|
33
|
+
resolve();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const onError = () => fail(new Error('Unable to read a frame from the captured screen.'));
|
|
37
|
+
const onAbort = () => fail(abortError());
|
|
38
|
+
const timer = setTimeout(onError, 10000);
|
|
39
|
+
video.addEventListener('loadeddata', onFrame);
|
|
40
|
+
video.addEventListener('error', onError);
|
|
41
|
+
track?.addEventListener('ended', onError);
|
|
42
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
43
|
+
if (signal?.aborted) {
|
|
44
|
+
onAbort();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!track || track.readyState === 'ended') {
|
|
48
|
+
onError();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
video.srcObject = stream;
|
|
53
|
+
video.play().then(() => { playing = true; onFrame(); }, fail);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
fail(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function captureScreenshot(options = {}) {
|
|
61
|
+
let stream;
|
|
62
|
+
let video;
|
|
63
|
+
try {
|
|
64
|
+
throwIfAborted(options.signal);
|
|
65
|
+
ensureBrowserSupport();
|
|
66
|
+
for (const size of [options.width, options.height]) {
|
|
67
|
+
if (size !== undefined && (!Number.isFinite(size) || size < 1))
|
|
68
|
+
throw new Error('Screenshot dimensions must be positive.');
|
|
69
|
+
}
|
|
70
|
+
stream = await abortable(navigator.mediaDevices.getDisplayMedia({
|
|
71
|
+
video: true,
|
|
72
|
+
audio: false,
|
|
73
|
+
...{ preferCurrentTab: options.preferCurrentTab ?? true },
|
|
74
|
+
}), options.signal, stopTracks);
|
|
75
|
+
throwIfAborted(options.signal);
|
|
76
|
+
video = document.createElement('video');
|
|
77
|
+
video.muted = true;
|
|
78
|
+
video.playsInline = true;
|
|
79
|
+
await waitForFrame(video, stream, options.signal);
|
|
80
|
+
const scale = Math.min(1, (options.width ?? video.videoWidth) / video.videoWidth, (options.height ?? video.videoHeight) / video.videoHeight);
|
|
81
|
+
const width = Math.max(1, Math.round(video.videoWidth * scale));
|
|
82
|
+
const height = Math.max(1, Math.round(video.videoHeight * scale));
|
|
83
|
+
const canvas = document.createElement('canvas');
|
|
84
|
+
canvas.width = width;
|
|
85
|
+
canvas.height = height;
|
|
86
|
+
const context = canvas.getContext('2d');
|
|
87
|
+
if (!context)
|
|
88
|
+
throw new Error('Unable to create a screenshot canvas.');
|
|
89
|
+
context.drawImage(video, 0, 0, width, height);
|
|
90
|
+
const blob = await abortable(new Promise((resolve, reject) => {
|
|
91
|
+
canvas.toBlob(value => value ? resolve(value) : reject(new Error('Unable to encode screenshot.')), 'image/png');
|
|
92
|
+
}), options.signal);
|
|
93
|
+
throwIfAborted(options.signal);
|
|
94
|
+
return { kind: 'screenshot', blob, contentType: blob.type, filename: 'screenshot.png', width, height };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw normalizeCaptureError(error);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
if (video) {
|
|
101
|
+
video.pause();
|
|
102
|
+
video.srcObject = null;
|
|
103
|
+
}
|
|
104
|
+
if (stream)
|
|
105
|
+
stopTracks(stream);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export class ScreenRecorder {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.phase = 'idle';
|
|
111
|
+
}
|
|
112
|
+
async start(options = {}) {
|
|
113
|
+
if (this.phase !== 'idle') {
|
|
114
|
+
throw new FeedbackCaptureError('recording-already-started', 'A recording is already starting, running, or stopping.');
|
|
115
|
+
}
|
|
116
|
+
this.phase = 'requesting';
|
|
117
|
+
this.completion = undefined;
|
|
118
|
+
let stream;
|
|
119
|
+
try {
|
|
120
|
+
throwIfAborted(options.signal);
|
|
121
|
+
ensureBrowserSupport();
|
|
122
|
+
if (typeof MediaRecorder === 'undefined')
|
|
123
|
+
throw new FeedbackCaptureError('unsupported', 'Screen recording is not supported in this browser.');
|
|
124
|
+
if (options.maxDurationMs !== undefined && (!Number.isFinite(options.maxDurationMs) || options.maxDurationMs <= 0 || options.maxDurationMs > 2147483647)) {
|
|
125
|
+
throw new Error('Maximum duration must be a positive number within the browser timer limit.');
|
|
126
|
+
}
|
|
127
|
+
const mimeType = options.mimeType && MediaRecorder.isTypeSupported(options.mimeType)
|
|
128
|
+
? options.mimeType : MIME_TYPES.find(type => MediaRecorder.isTypeSupported(type));
|
|
129
|
+
stream = await abortable(navigator.mediaDevices.getDisplayMedia({
|
|
130
|
+
video: options.video ?? true,
|
|
131
|
+
audio: options.audio ?? false,
|
|
132
|
+
}), options.signal, stopTracks);
|
|
133
|
+
throwIfAborted(options.signal);
|
|
134
|
+
const track = stream.getVideoTracks()[0];
|
|
135
|
+
if (!track || track.readyState === 'ended')
|
|
136
|
+
throw new Error('No live screen video track was returned.');
|
|
137
|
+
const recorder = new MediaRecorder(stream, {
|
|
138
|
+
...(mimeType ? { mimeType } : {}),
|
|
139
|
+
...(options.videoBitsPerSecond ? { videoBitsPerSecond: options.videoBitsPerSecond } : {}),
|
|
140
|
+
...(options.audioBitsPerSecond ? { audioBitsPerSecond: options.audioBitsPerSecond } : {}),
|
|
141
|
+
});
|
|
142
|
+
this.recorder = recorder;
|
|
143
|
+
const capturedStream = stream;
|
|
144
|
+
const chunks = [];
|
|
145
|
+
const startedAt = Date.now();
|
|
146
|
+
let stoppedAt;
|
|
147
|
+
let settled = false;
|
|
148
|
+
let timer;
|
|
149
|
+
let fail;
|
|
150
|
+
this.completion = new Promise((resolve, reject) => {
|
|
151
|
+
const cleanup = () => {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
recorder.removeEventListener('dataavailable', onData);
|
|
154
|
+
recorder.removeEventListener('stop', onStop);
|
|
155
|
+
recorder.removeEventListener('error', onError);
|
|
156
|
+
track.removeEventListener('ended', stop);
|
|
157
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
158
|
+
stopTracks(capturedStream);
|
|
159
|
+
this.recorder = undefined;
|
|
160
|
+
this.requestStop = undefined;
|
|
161
|
+
this.phase = 'idle';
|
|
162
|
+
};
|
|
163
|
+
fail = error => {
|
|
164
|
+
if (settled)
|
|
165
|
+
return;
|
|
166
|
+
settled = true;
|
|
167
|
+
if (recorder.state !== 'inactive') {
|
|
168
|
+
try {
|
|
169
|
+
recorder.stop();
|
|
170
|
+
}
|
|
171
|
+
catch { /* Track cleanup still releases capture. */ }
|
|
172
|
+
}
|
|
173
|
+
cleanup();
|
|
174
|
+
chunks.length = 0;
|
|
175
|
+
reject(normalizeCaptureError(error));
|
|
176
|
+
};
|
|
177
|
+
const onData = (event) => { if (event.data.size > 0)
|
|
178
|
+
chunks.push(event.data); };
|
|
179
|
+
const onStop = () => {
|
|
180
|
+
if (settled)
|
|
181
|
+
return;
|
|
182
|
+
const contentType = recorder.mimeType || chunks[0]?.type || 'video/webm';
|
|
183
|
+
const blob = new Blob(chunks, { type: contentType });
|
|
184
|
+
if (!blob.size) {
|
|
185
|
+
fail(new Error('The recording produced no data.'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const { width, height } = track.getSettings();
|
|
189
|
+
settled = true;
|
|
190
|
+
cleanup();
|
|
191
|
+
chunks.length = 0;
|
|
192
|
+
resolve({ kind: 'recording', blob, contentType,
|
|
193
|
+
filename: contentType.includes('mp4') ? 'recording.mp4' : 'recording.webm',
|
|
194
|
+
durationMs: (stoppedAt ?? Date.now()) - startedAt, width, height });
|
|
195
|
+
};
|
|
196
|
+
const onError = (event) => fail(event.error ?? event);
|
|
197
|
+
const onAbort = () => fail(abortError());
|
|
198
|
+
const stop = () => {
|
|
199
|
+
if (settled || this.phase === 'stopping')
|
|
200
|
+
return;
|
|
201
|
+
this.phase = 'stopping';
|
|
202
|
+
stoppedAt = Date.now();
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
try {
|
|
205
|
+
// An inactive recorder may still be dispatching its final data event.
|
|
206
|
+
// Only the stop event resolves the result, including for concurrent stops.
|
|
207
|
+
if (recorder.state !== 'inactive')
|
|
208
|
+
recorder.stop();
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
fail(error);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
this.requestStop = stop;
|
|
215
|
+
recorder.addEventListener('dataavailable', onData);
|
|
216
|
+
recorder.addEventListener('stop', onStop);
|
|
217
|
+
recorder.addEventListener('error', onError);
|
|
218
|
+
track.addEventListener('ended', stop);
|
|
219
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
220
|
+
if (options.maxDurationMs)
|
|
221
|
+
timer = setTimeout(stop, options.maxDurationMs);
|
|
222
|
+
});
|
|
223
|
+
// Keep automatic failures observable via finished/stop without an unhandled
|
|
224
|
+
// rejection when the host only requests the result after recording ends.
|
|
225
|
+
void this.completion.catch(() => { });
|
|
226
|
+
try {
|
|
227
|
+
recorder.start(1000);
|
|
228
|
+
this.phase = 'recording';
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
fail(error);
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (stream)
|
|
237
|
+
stopTracks(stream);
|
|
238
|
+
this.recorder = undefined;
|
|
239
|
+
this.requestStop = undefined;
|
|
240
|
+
this.phase = 'idle';
|
|
241
|
+
throw normalizeCaptureError(error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Resolves on manual, duration-limit, or browser stop; rejects on abort/error. */
|
|
245
|
+
get finished() {
|
|
246
|
+
return this.completion ?? Promise.reject(new FeedbackCaptureError('recording-not-started', 'There is no screen recording result.'));
|
|
247
|
+
}
|
|
248
|
+
stop() {
|
|
249
|
+
this.requestStop?.();
|
|
250
|
+
return this.finished;
|
|
251
|
+
}
|
|
252
|
+
get isRecording() {
|
|
253
|
+
return this.phase === 'recording' && this.recorder?.state === 'recording';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ScreenRecorder } from './capture.js';
|
|
2
|
+
import type { CapturedAsset, FeedbackSubmission, FeedbackTransport, RecordingOptions, ScreenshotOptions } from './types.js';
|
|
3
|
+
export declare class FeedbackClient {
|
|
4
|
+
private readonly transport;
|
|
5
|
+
readonly recorder: ScreenRecorder;
|
|
6
|
+
constructor(transport: FeedbackTransport);
|
|
7
|
+
captureScreenshot(options?: ScreenshotOptions): Promise<CapturedAsset>;
|
|
8
|
+
startRecording(options?: RecordingOptions): Promise<void>;
|
|
9
|
+
stopRecording(): Promise<CapturedAsset>;
|
|
10
|
+
submit(input: FeedbackSubmission): Promise<{
|
|
11
|
+
id?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAGb,OAAO,CAAC,QAAQ,CAAC,SAAS;IAFtC,QAAQ,CAAC,QAAQ,iBAAwB;gBAEZ,SAAS,EAAE,iBAAiB;IAEzD,iBAAiB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAItE,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC;IAIjC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAiBlE"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { captureScreenshot, ScreenRecorder } from './capture.js';
|
|
2
|
+
import { abortable, throwIfAborted } from './abort.js';
|
|
3
|
+
export class FeedbackClient {
|
|
4
|
+
constructor(transport) {
|
|
5
|
+
this.transport = transport;
|
|
6
|
+
this.recorder = new ScreenRecorder();
|
|
7
|
+
}
|
|
8
|
+
captureScreenshot(options) {
|
|
9
|
+
return captureScreenshot(options);
|
|
10
|
+
}
|
|
11
|
+
startRecording(options) {
|
|
12
|
+
return this.recorder.start(options);
|
|
13
|
+
}
|
|
14
|
+
stopRecording() {
|
|
15
|
+
return this.recorder.stop();
|
|
16
|
+
}
|
|
17
|
+
async submit(input) {
|
|
18
|
+
throwIfAborted(input.signal);
|
|
19
|
+
const assets = [input.screenshot, input.recording].filter((asset) => Boolean(asset));
|
|
20
|
+
const uploaded = await abortable(Promise.all(assets.map(async (asset) => ({
|
|
21
|
+
kind: asset.kind,
|
|
22
|
+
...(await this.transport.uploadAsset({ asset, signal: input.signal })),
|
|
23
|
+
}))), input.signal);
|
|
24
|
+
throwIfAborted(input.signal);
|
|
25
|
+
return abortable(this.transport.submitFeedback({ message: input.message, assets: uploaded, context: input.context }, { signal: input.signal }), input.signal);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASvD,MAAM,OAAO,cAAc;IAGzB,YAA6B,SAA4B;QAA5B,cAAS,GAAT,SAAS,CAAmB;QAFhD,aAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IAEmB,CAAC;IAE7D,iBAAiB,CAAC,OAA2B;QAC3C,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,cAAc,CAAC,OAA0B;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAyB;QACpC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CACvD,CAAC,KAAK,EAA0B,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAClD,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,CAC1C,MAAM,CAAC,GAAG,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE,CAAC,CAAC;YACzB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;SACvE,CAAC,CAAC,CACJ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACjB,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7B,OAAO,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAC5C,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EACpE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CACzB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnB,CAAC;CACF"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type FeedbackCaptureErrorCode = 'unsupported' | 'permission-denied' | 'cancelled' | 'capture-failed' | 'recording-not-started' | 'recording-already-started';
|
|
2
|
+
export declare class FeedbackCaptureError extends Error {
|
|
3
|
+
readonly code: FeedbackCaptureErrorCode;
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
constructor(code: FeedbackCaptureErrorCode, message: string, cause?: unknown);
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeCaptureError(error: unknown): FeedbackCaptureError;
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAChC,aAAa,GACb,mBAAmB,GACnB,WAAW,GACX,gBAAgB,GAChB,uBAAuB,GACvB,2BAA2B,CAAC;AAEhC,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEb,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM7E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAa1E"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class FeedbackCaptureError extends Error {
|
|
2
|
+
constructor(code, message, cause) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'FeedbackCaptureError';
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.cause = cause;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function normalizeCaptureError(error) {
|
|
10
|
+
if (error instanceof FeedbackCaptureError)
|
|
11
|
+
return error;
|
|
12
|
+
if (typeof DOMException !== 'undefined' && error instanceof DOMException) {
|
|
13
|
+
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
|
|
14
|
+
return new FeedbackCaptureError('permission-denied', 'Screen capture permission was denied.', error);
|
|
15
|
+
}
|
|
16
|
+
if (error.name === 'AbortError') {
|
|
17
|
+
return new FeedbackCaptureError('cancelled', 'Screen capture was cancelled.', error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return new FeedbackCaptureError('capture-failed', 'Screen capture failed.', error);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAI7C,YAAY,IAA8B,EAAE,OAAe,EAAE,KAAe;QAC1E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,KAAK,YAAY,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAExD,IAAI,OAAO,YAAY,KAAK,WAAW,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;QACzE,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YAC/E,OAAO,IAAI,oBAAoB,CAAC,mBAAmB,EAAE,uCAAuC,EAAE,KAAK,CAAC,CAAC;QACvG,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAChC,OAAO,IAAI,oBAAoB,CAAC,WAAW,EAAE,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,OAAO,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,KAAK,CAAC,CAAC;AACrF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FeedbackCaptureError } from './errors.js';
|
|
2
|
+
export type { FeedbackCaptureErrorCode } from './errors.js';
|
|
3
|
+
export { captureScreenshot, ScreenRecorder } from './capture.js';
|
|
4
|
+
export { FeedbackClient } from './client.js';
|
|
5
|
+
export type { CaptureKind, CapturedAsset, FeedbackPayload, FeedbackSubmission, FeedbackTransport, RecordingOptions, ScreenshotOptions, UploadAssetInput, UploadedAsset, } from './types.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,WAAW,EACX,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type CaptureKind = 'screenshot' | 'recording';
|
|
2
|
+
export interface CapturedAsset {
|
|
3
|
+
kind: CaptureKind;
|
|
4
|
+
blob: Blob;
|
|
5
|
+
contentType: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface UploadedAsset {
|
|
12
|
+
url: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface UploadAssetInput {
|
|
16
|
+
asset: CapturedAsset;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
export interface FeedbackPayload {
|
|
20
|
+
message: string;
|
|
21
|
+
assets: Array<{
|
|
22
|
+
kind: CaptureKind;
|
|
23
|
+
url: string;
|
|
24
|
+
id?: string;
|
|
25
|
+
}>;
|
|
26
|
+
context?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface FeedbackTransport {
|
|
29
|
+
uploadAsset(input: UploadAssetInput): Promise<UploadedAsset>;
|
|
30
|
+
submitFeedback(payload: FeedbackPayload, options?: {
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
id?: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface ScreenshotOptions {
|
|
37
|
+
/** Maximum output size. Defaults to captured pixels; aspect ratio is preserved. */
|
|
38
|
+
width?: number;
|
|
39
|
+
height?: number;
|
|
40
|
+
/** Prefer the current browser tab in Chromium's picker when supported. */
|
|
41
|
+
preferCurrentTab?: boolean;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
export interface RecordingOptions {
|
|
45
|
+
audio?: boolean;
|
|
46
|
+
video?: MediaTrackConstraints;
|
|
47
|
+
mimeType?: string;
|
|
48
|
+
videoBitsPerSecond?: number;
|
|
49
|
+
audioBitsPerSecond?: number;
|
|
50
|
+
/** Stops the recorder automatically after this duration. */
|
|
51
|
+
maxDurationMs?: number;
|
|
52
|
+
/** Cancels capture and discards the recording. Use stop() to keep the video. */
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}
|
|
55
|
+
export interface FeedbackSubmission {
|
|
56
|
+
message: string;
|
|
57
|
+
screenshot?: CapturedAsset;
|
|
58
|
+
recording?: CapturedAsset;
|
|
59
|
+
context?: Record<string, unknown>;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,WAAW,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,WAAW,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7D,cAAc,CACZ,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "artarch-feedback-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless browser feedback capture SDK for screenshots and screen recordings.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"test": "npm run build && node --test --test-timeout=15000 tests/*.test.mjs",
|
|
26
|
+
"test:browser": "npm run build && playwright test",
|
|
27
|
+
"test:all": "npm test && npm run test:browser",
|
|
28
|
+
"prepack": "npm run build",
|
|
29
|
+
"prepublishOnly": "npm run test:all"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@playwright/test": "1.63.0",
|
|
33
|
+
"typescript": "5.8.3"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|