@qaiddev/thumbs-embed 1.3.0 → 1.4.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/dist/bootstrap.d.ts +15 -0
- package/dist/chunks/annotate-BNibaDg3.js +367 -0
- package/dist/chunks/annotate-BNibaDg3.js.map +1 -0
- package/dist/chunks/bootstrap-U4yiPm5-.js +1099 -0
- package/dist/chunks/bootstrap-U4yiPm5-.js.map +1 -0
- package/dist/chunks/modal-BX5wqy4s.js +119 -0
- package/dist/chunks/modal-BX5wqy4s.js.map +1 -0
- package/dist/chunks/network-capture-DMbMwwwR.js +63 -0
- package/dist/chunks/network-capture-DMbMwwwR.js.map +1 -0
- package/dist/chunks/recording--IwOtRL2.js +256 -0
- package/dist/chunks/recording--IwOtRL2.js.map +1 -0
- package/dist/chunks/screenshot-BkOCLuS2.js +33 -0
- package/dist/chunks/screenshot-BkOCLuS2.js.map +1 -0
- package/dist/chunks/screenshot-dom-CME7tYRE.js +63 -0
- package/dist/chunks/screenshot-dom-CME7tYRE.js.map +1 -0
- package/dist/chunks/targeting-DwdDr9Cm.js +295 -0
- package/dist/chunks/targeting-DwdDr9Cm.js.map +1 -0
- package/dist/chunks/video-BOYqf2Im.js +209 -0
- package/dist/chunks/video-BOYqf2Im.js.map +1 -0
- package/dist/dom.d.ts +36 -0
- package/dist/embed.d.ts +53 -68
- package/dist/index.d.ts +10 -12
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +18 -0
- package/dist/loader.js +7 -0
- package/dist/loader.js.map +1 -0
- package/dist/modal.d.ts +49 -0
- package/dist/qaid.umd.cjs +23 -23
- package/dist/qaid.umd.cjs.map +1 -1
- package/dist/recording.d.ts +74 -0
- package/dist/targeting.d.ts +72 -0
- package/dist/types.d.ts +7 -1
- package/dist/video-capture.d.ts +7 -0
- package/dist/video-orientation.d.ts +2 -0
- package/dist/video-redaction.d.ts +58 -0
- package/package.json +9 -5
- package/dist/qaid.js +0 -2013
- package/dist/qaid.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video-recording subsystem — extracted from embed.ts so it lives in its own
|
|
3
|
+
* lazily-loaded chunk. A thumbs-only visitor never downloads any of this; the
|
|
4
|
+
* embed dynamically imports it the first time the record button is used (and
|
|
5
|
+
* warms it on hover — see embed.ts prewarmVideo).
|
|
6
|
+
*
|
|
7
|
+
* The controller owns all recording state and UI (recorder, redaction picker,
|
|
8
|
+
* indicator, preview, submit). It talks back to the embed through the narrow
|
|
9
|
+
* `RecordingHost` interface, so embed internals stay private and the coupling
|
|
10
|
+
* is explicit.
|
|
11
|
+
*/
|
|
12
|
+
import type { ResolvedFeedbackConfig, EmbedState } from "./types";
|
|
13
|
+
import type { ConsoleCapture } from "./console-capture";
|
|
14
|
+
/** The slice of the embed the recording subsystem needs. */
|
|
15
|
+
export interface RecordingHost {
|
|
16
|
+
readonly config: ResolvedFeedbackConfig;
|
|
17
|
+
readonly visitorId: string;
|
|
18
|
+
readonly uid: string;
|
|
19
|
+
readonly overlayShadowHost: HTMLDivElement | null;
|
|
20
|
+
readonly consoleCapture: ConsoleCapture | null;
|
|
21
|
+
readonly boundKeyDown: (e: KeyboardEvent) => void;
|
|
22
|
+
readonly state: EmbedState;
|
|
23
|
+
setState(state: EmbedState): void;
|
|
24
|
+
ensureOverlayHost(): ShadowRoot;
|
|
25
|
+
applyVars(el: HTMLElement): void;
|
|
26
|
+
announceMsg(message: string, assertive?: boolean): void;
|
|
27
|
+
openDialogA11y(container: HTMLElement, opts: {
|
|
28
|
+
labelledbyId?: string;
|
|
29
|
+
describedbyId?: string;
|
|
30
|
+
label?: string;
|
|
31
|
+
}): void;
|
|
32
|
+
closeDialogA11y(): void;
|
|
33
|
+
setButtonsDisabled(disabled: boolean): void;
|
|
34
|
+
tryLaunchQuest(type: "up" | "down" | "video", feedbackId: number | string | null): Promise<boolean>;
|
|
35
|
+
}
|
|
36
|
+
export declare class RecordingController {
|
|
37
|
+
private host;
|
|
38
|
+
private videoRecorder;
|
|
39
|
+
private networkCapture;
|
|
40
|
+
private recordedBlob;
|
|
41
|
+
private recordingIndicator;
|
|
42
|
+
private videoPreview;
|
|
43
|
+
private isRecording;
|
|
44
|
+
private isSendingVideo;
|
|
45
|
+
private redactPicks;
|
|
46
|
+
private redactPickerRoot;
|
|
47
|
+
private redactRafId;
|
|
48
|
+
private boundRedactClick;
|
|
49
|
+
constructor(host: RecordingHost);
|
|
50
|
+
/** Escape-key handler; returns true when it consumed the key. */
|
|
51
|
+
handleEscape(): boolean;
|
|
52
|
+
destroy(): void;
|
|
53
|
+
startRecording(redactionElements?: Element[]): Promise<void>;
|
|
54
|
+
private stopRecording;
|
|
55
|
+
/**
|
|
56
|
+
* Pre-recording redaction picker. The user clicks page areas to blur; each
|
|
57
|
+
* gets an outline that tracks its position, then "Start recording" hands the
|
|
58
|
+
* picks to the recorder, which blurs their live bounding boxes so the
|
|
59
|
+
* redaction follows the content as the page scrolls. "Cancel"/Escape aborts.
|
|
60
|
+
*/
|
|
61
|
+
startPicking(): void;
|
|
62
|
+
private handleRedactPickClick;
|
|
63
|
+
private renderRedactPicks;
|
|
64
|
+
private finishRedactionPicking;
|
|
65
|
+
private showRecordingIndicator;
|
|
66
|
+
private updateRecordingTimer;
|
|
67
|
+
private formatTime;
|
|
68
|
+
private removeRecordingIndicator;
|
|
69
|
+
private showRecordingPreview;
|
|
70
|
+
private cancelRecordingPreview;
|
|
71
|
+
private removeVideoPreview;
|
|
72
|
+
private submitVideoFeedback;
|
|
73
|
+
private cleanupRecording;
|
|
74
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element-targeting subsystem — extracted from embed.ts into its own lazily
|
|
3
|
+
* loaded chunk (it also carries `element-selector`, ~14 KB). It's fetched the
|
|
4
|
+
* first time the user starts targeting (and pre-warmed on thumb-button hover,
|
|
5
|
+
* so the reticle appears without a perceptible wait).
|
|
6
|
+
*
|
|
7
|
+
* The controller owns the reticle/overlay, the mouse/touch/keyboard targeting
|
|
8
|
+
* flows, and the selected-element marker; it talks back to the embed through
|
|
9
|
+
* the narrow `TargetingHost` interface.
|
|
10
|
+
*/
|
|
11
|
+
import type { ResolvedFeedbackConfig, EmbedState, FeedbackData, SelectedBounds } from "./types";
|
|
12
|
+
/** The slice of the embed the targeting subsystem needs. */
|
|
13
|
+
export interface TargetingHost {
|
|
14
|
+
readonly config: ResolvedFeedbackConfig;
|
|
15
|
+
readonly cssVars: Record<string, string>;
|
|
16
|
+
readonly state: EmbedState;
|
|
17
|
+
readonly shadowHost: HTMLDivElement | null;
|
|
18
|
+
readonly overlayShadowHost: HTMLDivElement | null;
|
|
19
|
+
readonly boundKeyDown: (e: KeyboardEvent) => void;
|
|
20
|
+
/** Live references, mutated in place. */
|
|
21
|
+
readonly feedbackData: FeedbackData;
|
|
22
|
+
readonly selectedBounds: SelectedBounds;
|
|
23
|
+
setState(state: EmbedState): void;
|
|
24
|
+
ensureOverlayHost(): ShadowRoot;
|
|
25
|
+
applyVars(el: HTMLElement): void;
|
|
26
|
+
announceMsg(message: string, assertive?: boolean): void;
|
|
27
|
+
clearActiveThumb(): void;
|
|
28
|
+
submitFeedback(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare class TargetingController {
|
|
31
|
+
private host;
|
|
32
|
+
private mousePos;
|
|
33
|
+
private touchStartPos;
|
|
34
|
+
private overlayContainer;
|
|
35
|
+
private captureLayer;
|
|
36
|
+
private crosshairH;
|
|
37
|
+
private crosshairV;
|
|
38
|
+
private scope;
|
|
39
|
+
private highlightBox;
|
|
40
|
+
private marker;
|
|
41
|
+
private keyboardController;
|
|
42
|
+
private boundMouseMove;
|
|
43
|
+
private boundClick;
|
|
44
|
+
private boundTouchStart;
|
|
45
|
+
private boundTouchEnd;
|
|
46
|
+
constructor(host: TargetingHost);
|
|
47
|
+
startPointer(type: "up" | "down", e: MouseEvent): void;
|
|
48
|
+
/**
|
|
49
|
+
* Keyboard-driven targeting. Mirrors startPointer minus the mouse plumbing:
|
|
50
|
+
* no `qaid-targeting` body class (keeps the cursor visible for keyboard
|
|
51
|
+
* users), no mouse reticle, and no document mouse/click listeners. The
|
|
52
|
+
* KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.
|
|
53
|
+
*/
|
|
54
|
+
startKeyboard(type: "up" | "down"): void;
|
|
55
|
+
private selectKeyboardTarget;
|
|
56
|
+
private createTargetingOverlay;
|
|
57
|
+
private handleMouseMove;
|
|
58
|
+
/** Move the crosshair/scope reticle and highlight the element under (x, y). */
|
|
59
|
+
private updateReticleAt;
|
|
60
|
+
private handleClick;
|
|
61
|
+
private handleTouchStart;
|
|
62
|
+
private handleTouchEnd;
|
|
63
|
+
/** Select the element under (x, y) and tear down targeting. */
|
|
64
|
+
private selectAt;
|
|
65
|
+
private stopTargetingListeners;
|
|
66
|
+
cancel(): void;
|
|
67
|
+
private removeTargetingOverlay;
|
|
68
|
+
private showSelectedMarker;
|
|
69
|
+
hideMarker(): void;
|
|
70
|
+
/** Full teardown for embed.destroy(). */
|
|
71
|
+
destroy(): void;
|
|
72
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -80,6 +80,11 @@ export interface FeedbackConfig {
|
|
|
80
80
|
videoOptions?: {
|
|
81
81
|
/** Max recording duration in seconds. Default: 15 */
|
|
82
82
|
maxDuration?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Let the user click page areas to blur before recording; the blur tracks
|
|
85
|
+
* each area as the page scrolls. Default: false.
|
|
86
|
+
*/
|
|
87
|
+
redaction?: boolean;
|
|
83
88
|
};
|
|
84
89
|
/** Custom SVG string for record button icon */
|
|
85
90
|
recordIcon?: string;
|
|
@@ -188,7 +193,7 @@ export interface FeedbackData {
|
|
|
188
193
|
/**
|
|
189
194
|
* State machine states for the embed
|
|
190
195
|
*/
|
|
191
|
-
export type EmbedState = "IDLE" | "TARGETING" | "SELECTED" | "MODAL_OPEN";
|
|
196
|
+
export type EmbedState = "IDLE" | "TARGETING" | "SELECTED" | "MODAL_OPEN" | "REDACT_PICKING";
|
|
192
197
|
/**
|
|
193
198
|
* Element bounds for server-side screenshot fallback
|
|
194
199
|
*/
|
|
@@ -293,6 +298,7 @@ export interface ResolvedFeedbackConfig {
|
|
|
293
298
|
captureVideo: boolean;
|
|
294
299
|
videoOptions: {
|
|
295
300
|
maxDuration: number;
|
|
301
|
+
redaction: boolean;
|
|
296
302
|
};
|
|
297
303
|
recordIcon: string;
|
|
298
304
|
quests: {
|
package/dist/video-capture.d.ts
CHANGED
|
@@ -7,6 +7,13 @@ export interface VideoRecorderOptions {
|
|
|
7
7
|
maxDuration?: number;
|
|
8
8
|
/** Video bitrate in bps. Default: 800000 (800kbps) */
|
|
9
9
|
videoBitsPerSecond?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Elements whose live bounding boxes are blurred out of the recording. The
|
|
12
|
+
* blur tracks each element as the page scrolls. Empty/omitted = no redaction.
|
|
13
|
+
*/
|
|
14
|
+
redactionElements?: Element[];
|
|
15
|
+
/** Blur radius in px for redacted regions. Default: 12. */
|
|
16
|
+
redactionBlurRadius?: number;
|
|
10
17
|
}
|
|
11
18
|
export interface VideoRecorder {
|
|
12
19
|
/** Start recording. Requests getDisplayMedia if not already started. */
|
|
@@ -40,6 +40,8 @@ export declare function isIOSDevice(): boolean;
|
|
|
40
40
|
export declare function computeOrientationTransform(frameW: number, frameH: number, viewportW: number, viewportH: number, screenAngle: number): OrientationTransform;
|
|
41
41
|
/** Draw one video frame onto the canvas context, applying the transform. */
|
|
42
42
|
export declare function drawRotatedFrame(ctx: CanvasRenderingContext2D, source: CanvasImageSource, t: OrientationTransform): void;
|
|
43
|
+
export declare function waitForVideoDimensions(video: HTMLVideoElement): Promise<void>;
|
|
44
|
+
export declare function playSilently(video: HTMLVideoElement): Promise<void>;
|
|
43
45
|
/**
|
|
44
46
|
* Build a canvas-backed, orientation-normalised copy of a display-capture
|
|
45
47
|
* stream. Returns null when the browser can't support the canvas pipeline, in
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live video redaction.
|
|
3
|
+
*
|
|
4
|
+
* Blurs the on-screen bounding boxes of chosen elements while the current tab
|
|
5
|
+
* is recorded. Every frame re-reads each element's `getBoundingClientRect()`,
|
|
6
|
+
* so the blur tracks the content as the page scrolls or reflows — no pixel
|
|
7
|
+
* tracking and no post-recording re-encode. The captured frame is the tab's
|
|
8
|
+
* viewport (video-capture restricts capture to the current tab), so viewport
|
|
9
|
+
* CSS coordinates map onto the frame with a single scale factor.
|
|
10
|
+
*
|
|
11
|
+
* The display stream is drawn to a canvas, the tracked regions are blurred on
|
|
12
|
+
* top, and MediaRecorder records the canvas stream instead of the raw capture —
|
|
13
|
+
* mirroring the iOS orientation pipeline in `video-orientation.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export interface RedactedStream {
|
|
16
|
+
/** Canvas-sourced stream to hand to MediaRecorder. */
|
|
17
|
+
stream: MediaStream;
|
|
18
|
+
/** Stop drawing and release the canvas stream + source video. */
|
|
19
|
+
stop: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface RedactionOptions {
|
|
22
|
+
/** Output frame rate of the redacted stream. Default: 15. */
|
|
23
|
+
frameRate?: number;
|
|
24
|
+
/** Gaussian blur radius in px applied to each region. Default: 12. */
|
|
25
|
+
blurRadius?: number;
|
|
26
|
+
}
|
|
27
|
+
interface Box {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
w: number;
|
|
31
|
+
h: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Map a viewport rect (CSS px, from `getBoundingClientRect()`) onto the
|
|
35
|
+
* captured frame, clamped to the frame bounds. Returns null when the rect is
|
|
36
|
+
* fully outside the viewport (the element scrolled away) or has no area.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mapRectToFrame(rect: {
|
|
39
|
+
left: number;
|
|
40
|
+
top: number;
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
}, scaleX: number, scaleY: number, frameW: number, frameH: number): Box | null;
|
|
44
|
+
/**
|
|
45
|
+
* Draw one frame: the full capture, then each element's live bounding box
|
|
46
|
+
* blurred on top. Exported for unit testing the per-frame logic without a real
|
|
47
|
+
* capture stream. When the 2D context can't apply a blur filter, the region is
|
|
48
|
+
* filled solid instead so sensitive content is still hidden.
|
|
49
|
+
*/
|
|
50
|
+
export declare function drawRedactedFrame(ctx: CanvasRenderingContext2D, video: CanvasImageSource, elements: Element[], frameW: number, frameH: number, blurRadius: number, supportsFilter: boolean): void;
|
|
51
|
+
/**
|
|
52
|
+
* Build a canvas-backed copy of a display-capture stream that blurs the live
|
|
53
|
+
* bounding boxes of `elements`. Returns null when the browser can't support the
|
|
54
|
+
* canvas pipeline (no `captureStream` / 2D context), in which case the caller
|
|
55
|
+
* should record the original stream unredacted.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildRedactedStream(displayStream: MediaStream, elements: Element[], options?: RedactionOptions): Promise<RedactedStream | null>;
|
|
58
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaiddev/thumbs-embed",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Precision User Feedback",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/qaid.umd.cjs",
|
|
7
|
-
"module": "./dist/
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/qaid.umd.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./loader": {
|
|
16
|
+
"types": "./dist/loader.d.ts",
|
|
17
|
+
"import": "./dist/loader.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
@@ -25,8 +29,8 @@
|
|
|
25
29
|
"url": "https://github.com/qaiddev/thumbs-embed/issues"
|
|
26
30
|
},
|
|
27
31
|
"scripts": {
|
|
28
|
-
"dev": "vite build --watch",
|
|
29
|
-
"build": "vite build && tsc -p tsconfig.build.json",
|
|
32
|
+
"dev": "vite build --watch --config vite.config.esm.ts",
|
|
33
|
+
"build": "vite build && vite build --config vite.config.esm.ts && tsc -p tsconfig.build.json",
|
|
30
34
|
"preview": "vite preview",
|
|
31
35
|
"test": "vitest",
|
|
32
36
|
"test:run": "vitest run",
|