recordable 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 +303 -0
- package/dist/actions.d.ts +140 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +184 -0
- package/dist/actions.js.map +1 -0
- package/dist/audio/track.d.ts +45 -0
- package/dist/audio/track.d.ts.map +1 -0
- package/dist/audio/track.js +61 -0
- package/dist/audio/track.js.map +1 -0
- package/dist/browser/cursor.d.ts +33 -0
- package/dist/browser/cursor.d.ts.map +1 -0
- package/dist/browser/cursor.js +118 -0
- package/dist/browser/cursor.js.map +1 -0
- package/dist/browser/dom.d.ts +31 -0
- package/dist/browser/dom.d.ts.map +1 -0
- package/dist/browser/dom.js +134 -0
- package/dist/browser/dom.js.map +1 -0
- package/dist/browser/play-button.d.ts +11 -0
- package/dist/browser/play-button.d.ts.map +1 -0
- package/dist/browser/play-button.js +87 -0
- package/dist/browser/play-button.js.map +1 -0
- package/dist/browser/runtime.d.ts +66 -0
- package/dist/browser/runtime.d.ts.map +1 -0
- package/dist/browser/runtime.js +271 -0
- package/dist/browser/runtime.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose/mix.d.ts +13 -0
- package/dist/compose/mix.d.ts.map +1 -0
- package/dist/compose/mix.js +50 -0
- package/dist/compose/mix.js.map +1 -0
- package/dist/compose/recordable.d.ts +149 -0
- package/dist/compose/recordable.d.ts.map +1 -0
- package/dist/compose/recordable.js +337 -0
- package/dist/compose/recordable.js.map +1 -0
- package/dist/compose/session.d.ts +38 -0
- package/dist/compose/session.d.ts.map +1 -0
- package/dist/compose/session.js +122 -0
- package/dist/compose/session.js.map +1 -0
- package/dist/config.d.ts +93 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/ffmpeg.d.ts +8 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +55 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/formats/json.d.ts +12 -0
- package/dist/formats/json.d.ts.map +1 -0
- package/dist/formats/json.js +20 -0
- package/dist/formats/json.js.map +1 -0
- package/dist/formats/markdown/method.d.ts +25 -0
- package/dist/formats/markdown/method.d.ts.map +1 -0
- package/dist/formats/markdown/method.js +48 -0
- package/dist/formats/markdown/method.js.map +1 -0
- package/dist/formats/markdown/parse.d.ts +44 -0
- package/dist/formats/markdown/parse.d.ts.map +1 -0
- package/dist/formats/markdown/parse.js +143 -0
- package/dist/formats/markdown/parse.js.map +1 -0
- package/dist/fs.d.ts +9 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +30 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/dist/script.d.ts +21 -0
- package/dist/script.d.ts.map +1 -0
- package/dist/script.js +26 -0
- package/dist/script.js.map +1 -0
- package/dist/targets.d.ts +6 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +13 -0
- package/dist/targets.js.map +1 -0
- package/dist/timing.d.ts +41 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +149 -0
- package/dist/timing.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +8 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +54 -0
- package/dist/validate.js.map +1 -0
- package/dist/video/recorder.d.ts +57 -0
- package/dist/video/recorder.d.ts.map +1 -0
- package/dist/video/recorder.js +238 -0
- package/dist/video/recorder.js.map +1 -0
- package/dist/video/stitch.d.ts +15 -0
- package/dist/video/stitch.d.ts.map +1 -0
- package/dist/video/stitch.js +111 -0
- package/dist/video/stitch.js.map +1 -0
- package/dist/voiceover/alignment.d.ts +14 -0
- package/dist/voiceover/alignment.d.ts.map +1 -0
- package/dist/voiceover/alignment.js +13 -0
- package/dist/voiceover/alignment.js.map +1 -0
- package/dist/voiceover/cache.d.ts +22 -0
- package/dist/voiceover/cache.d.ts.map +1 -0
- package/dist/voiceover/cache.js +55 -0
- package/dist/voiceover/cache.js.map +1 -0
- package/dist/voiceover/compile.d.ts +35 -0
- package/dist/voiceover/compile.d.ts.map +1 -0
- package/dist/voiceover/compile.js +194 -0
- package/dist/voiceover/compile.js.map +1 -0
- package/dist/voiceover/elevenlabs.d.ts +16 -0
- package/dist/voiceover/elevenlabs.d.ts.map +1 -0
- package/dist/voiceover/elevenlabs.js +66 -0
- package/dist/voiceover/elevenlabs.js.map +1 -0
- package/dist/voiceover/index.d.ts +7 -0
- package/dist/voiceover/index.d.ts.map +1 -0
- package/dist/voiceover/index.js +8 -0
- package/dist/voiceover/index.js.map +1 -0
- package/dist/voiceover/mock.d.ts +15 -0
- package/dist/voiceover/mock.d.ts.map +1 -0
- package/dist/voiceover/mock.js +41 -0
- package/dist/voiceover/mock.js.map +1 -0
- package/dist/voiceover/types.d.ts +31 -0
- package/dist/voiceover/types.d.ts.map +1 -0
- package/dist/voiceover/types.js +10 -0
- package/dist/voiceover/types.js.map +1 -0
- package/package.json +86 -0
- package/recordable.schema.json +738 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** One clip placed on the timeline (recorded-time milliseconds). */
|
|
2
|
+
export interface AudioClip {
|
|
3
|
+
path: string;
|
|
4
|
+
startMs: number;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
volume?: number;
|
|
7
|
+
}
|
|
8
|
+
/** An ordered set of audio clips, each pinned to a caller-supplied recorded-time
|
|
9
|
+
* position. Mixed onto the finished video at finalise by `mix.ts`. */
|
|
10
|
+
export declare class AudioTrack {
|
|
11
|
+
private readonly clips;
|
|
12
|
+
/** Number of clips on the track. */
|
|
13
|
+
get length(): number;
|
|
14
|
+
/** The clips in document order (read-only view for the mixer). */
|
|
15
|
+
list(): readonly AudioClip[];
|
|
16
|
+
/**
|
|
17
|
+
* Add a clip at `startMs` (recorded time). Probes its duration so the caller
|
|
18
|
+
* can block for it when `audio({ wait: true })`. Throws if the file is missing.
|
|
19
|
+
*/
|
|
20
|
+
add(path: string, startMs: number, options?: {
|
|
21
|
+
volume?: number;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
startMs: number;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the `filter_complex` chain that delays each clip to its `startMs`,
|
|
29
|
+
* applies volume, and mixes them. Input index `i+1` (input 0 is the video).
|
|
30
|
+
* Returns the filter parts and the label to `-map` as the output audio.
|
|
31
|
+
*/
|
|
32
|
+
export declare function audioFilterGraph(clips: readonly AudioClip[]): {
|
|
33
|
+
filters: string[];
|
|
34
|
+
mapLabel: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Clips that run past the video end (beyond `tolMs` slack). Per the timing
|
|
38
|
+
* contract the video defines the length, so an overrun is the author's cue to
|
|
39
|
+
* add a trailing `wait()`; the mixer warns and lets `-t` truncate.
|
|
40
|
+
*/
|
|
41
|
+
export declare function audioOverruns(clips: readonly AudioClip[], videoMs: number, tolMs?: number): {
|
|
42
|
+
path: string;
|
|
43
|
+
overMs: number;
|
|
44
|
+
}[];
|
|
45
|
+
//# sourceMappingURL=track.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/audio/track.ts"],"names":[],"mappings":"AAYA,oEAAoE;AACpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;uEACuE;AACvE,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IAEzC,oCAAoC;IACpC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,kEAAkE;IAClE,IAAI,IAAI,SAAS,SAAS,EAAE;IAI5B;;;OAGG;IACG,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAChC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAOpD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAeA;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,OAAO,EAAE,MAAM,EACf,KAAK,SAAK,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAOpC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { getDuration } from "../ffmpeg.js";
|
|
3
|
+
import { RecordableError } from "../errors.js";
|
|
4
|
+
/** An ordered set of audio clips, each pinned to a caller-supplied recorded-time
|
|
5
|
+
* position. Mixed onto the finished video at finalise by `mix.ts`. */
|
|
6
|
+
export class AudioTrack {
|
|
7
|
+
clips = [];
|
|
8
|
+
/** Number of clips on the track. */
|
|
9
|
+
get length() {
|
|
10
|
+
return this.clips.length;
|
|
11
|
+
}
|
|
12
|
+
/** The clips in document order (read-only view for the mixer). */
|
|
13
|
+
list() {
|
|
14
|
+
return this.clips;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add a clip at `startMs` (recorded time). Probes its duration so the caller
|
|
18
|
+
* can block for it when `audio({ wait: true })`. Throws if the file is missing.
|
|
19
|
+
*/
|
|
20
|
+
async add(path, startMs, options = {}) {
|
|
21
|
+
if (!existsSync(path))
|
|
22
|
+
throw new RecordableError("FILE_NOT_FOUND", `audio: file not found: ${path}`);
|
|
23
|
+
const durationMs = (await getDuration(path)) * 1000;
|
|
24
|
+
this.clips.push({ path, startMs, durationMs, volume: options.volume });
|
|
25
|
+
return { startMs, durationMs };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the `filter_complex` chain that delays each clip to its `startMs`,
|
|
30
|
+
* applies volume, and mixes them. Input index `i+1` (input 0 is the video).
|
|
31
|
+
* Returns the filter parts and the label to `-map` as the output audio.
|
|
32
|
+
*/
|
|
33
|
+
export function audioFilterGraph(clips) {
|
|
34
|
+
const labels = [];
|
|
35
|
+
const filters = clips.map((c, i) => {
|
|
36
|
+
const chain = [`adelay=${Math.round(c.startMs)}:all=1`];
|
|
37
|
+
if (c.volume != null && c.volume !== 1)
|
|
38
|
+
chain.push(`volume=${c.volume}`);
|
|
39
|
+
labels.push(`[a${i}]`);
|
|
40
|
+
return `[${i + 1}:a]${chain.join(",")}[a${i}]`;
|
|
41
|
+
});
|
|
42
|
+
if (clips.length === 1)
|
|
43
|
+
return { filters, mapLabel: "a0" };
|
|
44
|
+
filters.push(`${labels.join("")}amix=inputs=${clips.length}:normalize=0[aout]`);
|
|
45
|
+
return { filters, mapLabel: "aout" };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Clips that run past the video end (beyond `tolMs` slack). Per the timing
|
|
49
|
+
* contract the video defines the length, so an overrun is the author's cue to
|
|
50
|
+
* add a trailing `wait()`; the mixer warns and lets `-t` truncate.
|
|
51
|
+
*/
|
|
52
|
+
export function audioOverruns(clips, videoMs, tolMs = 50) {
|
|
53
|
+
const over = [];
|
|
54
|
+
for (const c of clips) {
|
|
55
|
+
const overMs = Math.round(c.startMs + c.durationMs - videoMs);
|
|
56
|
+
if (overMs > tolMs)
|
|
57
|
+
over.push({ path: c.path, overMs });
|
|
58
|
+
}
|
|
59
|
+
return over;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/audio/track.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAkB/C;uEACuE;AACvE,MAAM,OAAO,UAAU;IACJ,KAAK,GAAgB,EAAE,CAAC;IAEzC,oCAAoC;IACpC,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,kEAAkE;IAClE,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CACP,IAAY,EACZ,OAAe,EACf,UAA+B,EAAE;QAEjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACnB,MAAM,IAAI,eAAe,CAAC,gBAAgB,EAAE,0BAA0B,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QACpD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IACjC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA2B;IAI1D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE3D,OAAO,CAAC,IAAI,CACV,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,KAAK,CAAC,MAAM,oBAAoB,CAClE,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA2B,EAC3B,OAAe,EACf,KAAK,GAAG,EAAE;IAEV,MAAM,IAAI,GAAuC,EAAE,CAAC;IACpD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC;QAC9D,IAAI,MAAM,GAAG,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Page } from "puppeteer";
|
|
2
|
+
/** The page's current zoom transform, needed to position the overlay correctly. */
|
|
3
|
+
export interface ZoomState {
|
|
4
|
+
tx: number;
|
|
5
|
+
ty: number;
|
|
6
|
+
s: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* An animated cursor overlay drawn into the page. Tracks its own position so
|
|
10
|
+
* each move can ease from where it last was, and dips on click for a tactile
|
|
11
|
+
* press effect. Also drives the real Puppeteer mouse so hover/click still fire.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Cursor {
|
|
14
|
+
private pos;
|
|
15
|
+
/**
|
|
16
|
+
* Draw the cursor SVG and hide the native pointer. Idempotent per document.
|
|
17
|
+
* Renders the overlay at the last-known position (carried across navigations)
|
|
18
|
+
* and syncs the real mouse there so hover state stays consistent.
|
|
19
|
+
*/
|
|
20
|
+
inject(page: Page, zoom?: ZoomState): Promise<void>;
|
|
21
|
+
/** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
|
|
22
|
+
moveTo(page: Page, toX: number, toY: number, zoom: ZoomState): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Convert viewport coords → document coords for the overlay's transform.
|
|
25
|
+
* When documentElement has a CSS transform (zoom), position:fixed children
|
|
26
|
+
* are positioned relative to that ancestor (not the viewport) and scroll with
|
|
27
|
+
* the page, so we add scroll and apply the inverse zoom transform.
|
|
28
|
+
*/
|
|
29
|
+
private _toDocCoords;
|
|
30
|
+
/** Briefly scale the cursor down to signal a press. */
|
|
31
|
+
clickEffect(page: Page): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,qBAAa,MAAM;IAIjB,OAAO,CAAC,GAAG,CAAkB;IAE7B;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAE,SAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsDjF,0EAA0E;IACpE,MAAM,CACV,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC;IAiChB;;;;;OAKG;YACW,YAAY;IAa1B,uDAAuD;IACjD,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAa7C"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { sleep } from "../utils.js";
|
|
2
|
+
import { cursorMoveMs, PRESS_DOWN_MS, PRESS_SETTLE_MS } from "../timing.js";
|
|
3
|
+
const CURSOR_ID = "__recordable_cursor__";
|
|
4
|
+
/**
|
|
5
|
+
* An animated cursor overlay drawn into the page. Tracks its own position so
|
|
6
|
+
* each move can ease from where it last was, and dips on click for a tactile
|
|
7
|
+
* press effect. Also drives the real Puppeteer mouse so hover/click still fire.
|
|
8
|
+
*/
|
|
9
|
+
export class Cursor {
|
|
10
|
+
// Persisted across injects so the overlay survives navigation: a fresh page
|
|
11
|
+
// re-injects, and we want the cursor to reappear where it last was (like a
|
|
12
|
+
// real pointer) rather than snapping to the top-left corner.
|
|
13
|
+
pos = { x: 0, y: 0 };
|
|
14
|
+
/**
|
|
15
|
+
* Draw the cursor SVG and hide the native pointer. Idempotent per document.
|
|
16
|
+
* Renders the overlay at the last-known position (carried across navigations)
|
|
17
|
+
* and syncs the real mouse there so hover state stays consistent.
|
|
18
|
+
*/
|
|
19
|
+
async inject(page, zoom = { tx: 0, ty: 0, s: 1 }) {
|
|
20
|
+
// Bail early when there's nothing to do or we can't yet: already present, an
|
|
21
|
+
// iframe, or the new document's <body> hasn't parsed. A too-early call (e.g.
|
|
22
|
+
// from a navigation event) is a no-op; the next moveTo re-injects when ready.
|
|
23
|
+
const skip = await page.evaluate((id) => window !== window.parent ||
|
|
24
|
+
!document.body ||
|
|
25
|
+
!!document.getElementById(id), CURSOR_ID);
|
|
26
|
+
if (skip)
|
|
27
|
+
return;
|
|
28
|
+
const { cx, cy } = await this._toDocCoords(page, this.pos.x, this.pos.y, zoom);
|
|
29
|
+
await page.evaluate(({ id, cx, cy }) => {
|
|
30
|
+
const style = document.createElement("style");
|
|
31
|
+
style.textContent = `
|
|
32
|
+
* { cursor: none !important; }
|
|
33
|
+
#${id} {
|
|
34
|
+
position: fixed;
|
|
35
|
+
top: 0; left: 0;
|
|
36
|
+
margin: -2px 0 0 -4px;
|
|
37
|
+
z-index: 2147483647;
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
will-change: transform;
|
|
40
|
+
transition: transform 0.15s;
|
|
41
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4));
|
|
42
|
+
}
|
|
43
|
+
#${id}.pressing {
|
|
44
|
+
transform: var(--recordable-pos) scale(0.88) !important;
|
|
45
|
+
transition: transform 0.08s !important;
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
document.head.appendChild(style);
|
|
49
|
+
const cursor = document.createElement("div");
|
|
50
|
+
cursor.id = id;
|
|
51
|
+
// Place at the carried-over position with no transition, so it appears
|
|
52
|
+
// there immediately instead of animating in from the corner.
|
|
53
|
+
cursor.style.transition = "none";
|
|
54
|
+
cursor.style.setProperty("--recordable-pos", `translate(${cx}px, ${cy}px)`);
|
|
55
|
+
cursor.style.transform = `translate(${cx}px, ${cy}px)`;
|
|
56
|
+
cursor.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
|
57
|
+
<path d="M4 2 L4 19 L8.5 14.5 L12 22 L14 21 L10.5 13.5 L17 13.5 Z"
|
|
58
|
+
fill="white" stroke="#1e1b4b" stroke-width="1.2" stroke-linejoin="round"/>
|
|
59
|
+
</svg>`;
|
|
60
|
+
document.body.appendChild(cursor);
|
|
61
|
+
}, { id: CURSOR_ID, cx, cy });
|
|
62
|
+
await page.mouse.move(this.pos.x, this.pos.y);
|
|
63
|
+
}
|
|
64
|
+
/** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
|
|
65
|
+
async moveTo(page, toX, toY, zoom) {
|
|
66
|
+
// Self-heal: a navigation (incl. click-triggered ones with no following
|
|
67
|
+
// visit/waitFor) wipes the overlay. Re-inject at the carried position before
|
|
68
|
+
// animating, so the move is always visible — never an instant, cursor-less jump.
|
|
69
|
+
await this.inject(page, zoom);
|
|
70
|
+
const dx = toX - this.pos.x;
|
|
71
|
+
const dy = toY - this.pos.y;
|
|
72
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
73
|
+
const dur = cursorMoveMs(dist);
|
|
74
|
+
const { cx, cy } = await this._toDocCoords(page, toX, toY, zoom);
|
|
75
|
+
await page.evaluate(({ id, cx, cy, dur }) => new Promise((resolve) => {
|
|
76
|
+
const cursor = document.getElementById(id);
|
|
77
|
+
if (!cursor) {
|
|
78
|
+
resolve();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
cursor.style.transition = `transform ${dur}ms cubic-bezier(0.4,0,0.2,1)`;
|
|
82
|
+
cursor.style.setProperty("--recordable-pos", `translate(${cx}px, ${cy}px)`);
|
|
83
|
+
cursor.style.transform = `translate(${cx}px, ${cy}px)`;
|
|
84
|
+
setTimeout(resolve, dur);
|
|
85
|
+
}), { id: CURSOR_ID, cx, cy, dur });
|
|
86
|
+
await page.mouse.move(toX, toY);
|
|
87
|
+
this.pos = { x: toX, y: toY };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Convert viewport coords → document coords for the overlay's transform.
|
|
91
|
+
* When documentElement has a CSS transform (zoom), position:fixed children
|
|
92
|
+
* are positioned relative to that ancestor (not the viewport) and scroll with
|
|
93
|
+
* the page, so we add scroll and apply the inverse zoom transform.
|
|
94
|
+
*/
|
|
95
|
+
async _toDocCoords(page, x, y, { tx, ty, s }) {
|
|
96
|
+
const hasTransform = s !== 1 || tx !== 0 || ty !== 0;
|
|
97
|
+
const scroll = hasTransform
|
|
98
|
+
? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }))
|
|
99
|
+
: { x: 0, y: 0 };
|
|
100
|
+
return { cx: (x + scroll.x - tx) / s, cy: (y + scroll.y - ty) / s };
|
|
101
|
+
}
|
|
102
|
+
/** Briefly scale the cursor down to signal a press. */
|
|
103
|
+
async clickEffect(page) {
|
|
104
|
+
await page.evaluate((id) => {
|
|
105
|
+
const cursor = document.getElementById(id);
|
|
106
|
+
if (!cursor)
|
|
107
|
+
return;
|
|
108
|
+
cursor.classList.add("pressing");
|
|
109
|
+
void cursor.offsetWidth;
|
|
110
|
+
}, CURSOR_ID);
|
|
111
|
+
await sleep(PRESS_DOWN_MS);
|
|
112
|
+
await page.evaluate((id) => {
|
|
113
|
+
document.getElementById(id)?.classList.remove("pressing");
|
|
114
|
+
}, CURSOR_ID);
|
|
115
|
+
await sleep(PRESS_SETTLE_MS);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Page } from "puppeteer";
|
|
2
|
+
/** Coordinates in viewport pixels. */
|
|
3
|
+
export interface Point {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
/** Resolve a target and wait for the element to exist in the DOM, then return its handle. */
|
|
8
|
+
export declare function getHandle(page: Page, target: string): Promise<import("puppeteer").ElementHandle<Element>>;
|
|
9
|
+
/** Centre coords of a target element, jittered up to 20% of each dimension. */
|
|
10
|
+
export declare function getElementCenter(page: Page, target: string): Promise<Point>;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve an origin string to viewport pixel coordinates.
|
|
13
|
+
* Accepts CSS position keywords/percentages or an element selector.
|
|
14
|
+
*/
|
|
15
|
+
export declare function originToCoords(page: Page, origin: string): Promise<Point>;
|
|
16
|
+
/** Animate the page's scroll position to `targetY` over `duration` ms with an ease curve. */
|
|
17
|
+
export declare function smoothScroll(page: Page, targetY: number, duration: number): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Smooth-scroll to an element or position:
|
|
20
|
+
* - `"top"` / `"bottom"` → page extremes
|
|
21
|
+
* - number → absolute Y pixel position
|
|
22
|
+
* - CSS selector or `text:` prefix → element centred in the viewport
|
|
23
|
+
*/
|
|
24
|
+
export declare function smoothScrollToTarget(page: Page, target: string | number, duration: number): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Scroll `target` into view if it lies outside the visible viewport (keeping
|
|
27
|
+
* `margin` px clear on each side). No-op when the element is already fully
|
|
28
|
+
* visible. `speed` (px/s) sets the scroll duration.
|
|
29
|
+
*/
|
|
30
|
+
export declare function scrollIntoView(page: Page, target: string, margin: number, speed: number): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=dom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtC,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,6FAA6F;AAC7F,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,uDAMzD;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,CAShB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,CA4BhB;AAED,6FAA6F;AAC7F,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiCf"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { isPositionValue, resolveTarget } from "../targets.js";
|
|
2
|
+
/** Resolve a target and wait for the element to exist in the DOM, then return its handle. */
|
|
3
|
+
export async function getHandle(page, target) {
|
|
4
|
+
try {
|
|
5
|
+
return await page.locator(resolveTarget(target)).waitHandle();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error(`Could not find target: "${target}"`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Centre coords of a target element, jittered up to 20% of each dimension. */
|
|
12
|
+
export async function getElementCenter(page, target) {
|
|
13
|
+
const handle = await getHandle(page, target);
|
|
14
|
+
const box = await handle.boundingBox();
|
|
15
|
+
if (!box)
|
|
16
|
+
throw new Error(`No bounding box for "${target}"`);
|
|
17
|
+
const offset = (range) => (Math.random() - 0.5) * range * 0.4;
|
|
18
|
+
return {
|
|
19
|
+
x: box.x + box.width / 2 + offset(box.width),
|
|
20
|
+
y: box.y + box.height / 2 + offset(box.height),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve an origin string to viewport pixel coordinates.
|
|
25
|
+
* Accepts CSS position keywords/percentages or an element selector.
|
|
26
|
+
*/
|
|
27
|
+
export async function originToCoords(page, origin) {
|
|
28
|
+
if (!isPositionValue(origin))
|
|
29
|
+
return getElementCenter(page, origin);
|
|
30
|
+
return page.evaluate((origin) => {
|
|
31
|
+
const vw = window.innerWidth;
|
|
32
|
+
const vh = window.innerHeight;
|
|
33
|
+
const tokens = origin.trim().toLowerCase().split(/\s+/);
|
|
34
|
+
const kw = {
|
|
35
|
+
left: 0,
|
|
36
|
+
top: 0,
|
|
37
|
+
center: 50,
|
|
38
|
+
right: 100,
|
|
39
|
+
bottom: 100,
|
|
40
|
+
};
|
|
41
|
+
if (tokens.length === 1) {
|
|
42
|
+
const [t] = tokens;
|
|
43
|
+
const p = t in kw ? kw[t] : parseFloat(t);
|
|
44
|
+
if (t === "top" || t === "bottom")
|
|
45
|
+
return { x: vw / 2, y: (vh * p) / 100 };
|
|
46
|
+
if (t === "left" || t === "right")
|
|
47
|
+
return { x: (vw * p) / 100, y: vh / 2 };
|
|
48
|
+
return { x: (vw * p) / 100, y: (vh * p) / 100 };
|
|
49
|
+
}
|
|
50
|
+
const yAxis = ["top", "bottom"];
|
|
51
|
+
const [a, b] = yAxis.includes(tokens[0]) ? [tokens[1], tokens[0]] : tokens;
|
|
52
|
+
const px = a in kw ? kw[a] : parseFloat(a);
|
|
53
|
+
const py = b in kw ? kw[b] : parseFloat(b);
|
|
54
|
+
return { x: (vw * px) / 100, y: (vh * py) / 100 };
|
|
55
|
+
}, origin);
|
|
56
|
+
}
|
|
57
|
+
/** Animate the page's scroll position to `targetY` over `duration` ms with an ease curve. */
|
|
58
|
+
export async function smoothScroll(page, targetY, duration) {
|
|
59
|
+
await page.evaluate(({ targetY, duration }) => {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
const startY = window.scrollY;
|
|
62
|
+
const dist = targetY - startY;
|
|
63
|
+
const frames = Math.ceil(duration / 16);
|
|
64
|
+
let i = 0;
|
|
65
|
+
const id = setInterval(() => {
|
|
66
|
+
i++;
|
|
67
|
+
const p = Math.min(i / frames, 1);
|
|
68
|
+
const e = p < 0.5 ? 4 * p * p * p : (p - 1) * (2 * p - 2) * (2 * p - 2) + 1;
|
|
69
|
+
window.scrollTo(0, startY + dist * e);
|
|
70
|
+
if (p >= 1) {
|
|
71
|
+
clearInterval(id);
|
|
72
|
+
resolve();
|
|
73
|
+
}
|
|
74
|
+
}, 16);
|
|
75
|
+
});
|
|
76
|
+
}, { targetY, duration });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Smooth-scroll to an element or position:
|
|
80
|
+
* - `"top"` / `"bottom"` → page extremes
|
|
81
|
+
* - number → absolute Y pixel position
|
|
82
|
+
* - CSS selector or `text:` prefix → element centred in the viewport
|
|
83
|
+
*/
|
|
84
|
+
export async function smoothScrollToTarget(page, target, duration) {
|
|
85
|
+
if (typeof target === "number")
|
|
86
|
+
return smoothScroll(page, target, duration);
|
|
87
|
+
if (target === "top")
|
|
88
|
+
return smoothScroll(page, 0, duration);
|
|
89
|
+
if (target === "bottom") {
|
|
90
|
+
const bottom = await page.evaluate(() => document.body.scrollHeight);
|
|
91
|
+
return smoothScroll(page, bottom, duration);
|
|
92
|
+
}
|
|
93
|
+
const handle = await getHandle(page, target);
|
|
94
|
+
const y = await page.evaluate((el, vh) => {
|
|
95
|
+
const rect = el.getBoundingClientRect();
|
|
96
|
+
return window.scrollY + rect.top + rect.height / 2 - vh / 2;
|
|
97
|
+
}, handle, page.viewport()?.height ?? 900);
|
|
98
|
+
return smoothScroll(page, y, duration);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Scroll `target` into view if it lies outside the visible viewport (keeping
|
|
102
|
+
* `margin` px clear on each side). No-op when the element is already fully
|
|
103
|
+
* visible. `speed` (px/s) sets the scroll duration.
|
|
104
|
+
*/
|
|
105
|
+
export async function scrollIntoView(page, target, margin, speed) {
|
|
106
|
+
const handle = await getHandle(page, target);
|
|
107
|
+
const scrollY = await page.evaluate((el, margin) => {
|
|
108
|
+
const rect = el.getBoundingClientRect();
|
|
109
|
+
const vh = window.innerHeight;
|
|
110
|
+
const comfort = margin * 2;
|
|
111
|
+
if (rect.top >= comfort && rect.bottom <= vh - comfort)
|
|
112
|
+
return null;
|
|
113
|
+
// Tall element: top-align with margin
|
|
114
|
+
if (rect.height > vh - margin * 2)
|
|
115
|
+
return window.scrollY + rect.top - margin;
|
|
116
|
+
// Element extends below the bottom comfort zone: scroll just enough to
|
|
117
|
+
// show it fully, rather than trying to centre it (which often overshoots
|
|
118
|
+
// the page's max scroll and leaves the element at the viewport edge).
|
|
119
|
+
if (rect.bottom > vh - margin)
|
|
120
|
+
return window.scrollY + rect.bottom - (vh - margin);
|
|
121
|
+
// Element extends above the top comfort zone: scroll to show top
|
|
122
|
+
if (rect.top < margin)
|
|
123
|
+
return window.scrollY + rect.top - margin;
|
|
124
|
+
// In view but within the comfort band: centre it
|
|
125
|
+
return window.scrollY + rect.top + rect.height / 2 - vh / 2;
|
|
126
|
+
}, handle, margin);
|
|
127
|
+
if (scrollY === null)
|
|
128
|
+
return;
|
|
129
|
+
const currentY = await page.evaluate(() => window.scrollY);
|
|
130
|
+
const dist = Math.abs(scrollY - currentY);
|
|
131
|
+
const duration = Math.max(200, (dist / speed) * 1000);
|
|
132
|
+
await smoothScroll(page, scrollY, duration);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=dom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const PLAY_BUTTON_ID = "__recordable_play__";
|
|
2
|
+
export declare const PLAY_BINDING = "__recordablePlay__";
|
|
3
|
+
/**
|
|
4
|
+
* The button injection as a *string*, not the `injectPlayButton` function object.
|
|
5
|
+
* esbuild/tsx (via `keepNames`) rewrites the inner arrows to `__name(() => …)`;
|
|
6
|
+
* Puppeteer injects a function via `.toString()`, so that `__name` call would leak
|
|
7
|
+
* into the page and throw (it's undefined there). Wrapping in a closure that
|
|
8
|
+
* defines an `__name` shim makes the snippet bundler-proof.
|
|
9
|
+
*/
|
|
10
|
+
export declare function playButtonScript(message: string, id: string, binding: string): string;
|
|
11
|
+
//# sourceMappingURL=play-button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-button.d.ts","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,eAAO,MAAM,YAAY,uBAAuB,CAAC;AA0EjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,GACd,MAAM,CAGR"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const PLAY_BUTTON_ID = "__recordable_play__";
|
|
2
|
+
export const PLAY_BINDING = "__recordablePlay__";
|
|
3
|
+
/**
|
|
4
|
+
* Injected into the page to render the in-page ▶ Play button. Runs in the
|
|
5
|
+
* browser context, so it must be fully self-contained (no outer references).
|
|
6
|
+
*/
|
|
7
|
+
function injectPlayButton(message, id, binding) {
|
|
8
|
+
// Skip iframes — only the top document gets the button.
|
|
9
|
+
if (window !== window.parent)
|
|
10
|
+
return;
|
|
11
|
+
const build = () => {
|
|
12
|
+
if (document.getElementById(id))
|
|
13
|
+
return;
|
|
14
|
+
const wrap = document.createElement("div");
|
|
15
|
+
wrap.id = id;
|
|
16
|
+
wrap.style.cssText = [
|
|
17
|
+
"position:fixed",
|
|
18
|
+
"left:50%",
|
|
19
|
+
// Top-anchored: a headful window is shorter than the emulated viewport, so
|
|
20
|
+
// a bottom pill renders below the fold, out of the user's reach.
|
|
21
|
+
"top:24px",
|
|
22
|
+
"transform:translateX(-50%)",
|
|
23
|
+
"z-index:2147483647",
|
|
24
|
+
"pointer-events:auto",
|
|
25
|
+
"display:flex",
|
|
26
|
+
"align-items:center",
|
|
27
|
+
"gap:12px",
|
|
28
|
+
"padding:10px 18px 10px 12px",
|
|
29
|
+
"background:rgba(20,18,40,0.92)",
|
|
30
|
+
"color:#fff",
|
|
31
|
+
"border-radius:999px",
|
|
32
|
+
"box-shadow:0 8px 28px rgba(0,0,0,0.4)",
|
|
33
|
+
"font-family:system-ui,-apple-system,Segoe UI,sans-serif",
|
|
34
|
+
].join(";");
|
|
35
|
+
const btn = document.createElement("button");
|
|
36
|
+
btn.style.cssText = [
|
|
37
|
+
"all:unset",
|
|
38
|
+
"cursor:pointer",
|
|
39
|
+
"display:inline-flex",
|
|
40
|
+
"align-items:center",
|
|
41
|
+
"justify-content:center",
|
|
42
|
+
"width:34px",
|
|
43
|
+
"height:34px",
|
|
44
|
+
"border-radius:50%",
|
|
45
|
+
"background:#5b54e8",
|
|
46
|
+
"color:#fff",
|
|
47
|
+
"font-size:13px",
|
|
48
|
+
].join(";");
|
|
49
|
+
btn.textContent = "▶";
|
|
50
|
+
const label = document.createElement("span");
|
|
51
|
+
label.textContent = message;
|
|
52
|
+
label.style.cssText =
|
|
53
|
+
"font-size:14px;line-height:1;white-space:nowrap;cursor:default";
|
|
54
|
+
wrap.appendChild(btn);
|
|
55
|
+
wrap.appendChild(label);
|
|
56
|
+
let fired = false;
|
|
57
|
+
const fire = () => {
|
|
58
|
+
if (fired)
|
|
59
|
+
return;
|
|
60
|
+
fired = true;
|
|
61
|
+
wrap.remove();
|
|
62
|
+
const fn = window[binding];
|
|
63
|
+
if (typeof fn === "function")
|
|
64
|
+
fn();
|
|
65
|
+
};
|
|
66
|
+
// Click only — no Enter/Space binding, so the live page keeps its own
|
|
67
|
+
// keyboard (e.g. Enter to submit a form) during the manual step.
|
|
68
|
+
btn.addEventListener("click", fire);
|
|
69
|
+
document.body.appendChild(wrap);
|
|
70
|
+
};
|
|
71
|
+
if (document.body)
|
|
72
|
+
build();
|
|
73
|
+
else
|
|
74
|
+
document.addEventListener("DOMContentLoaded", build);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The button injection as a *string*, not the `injectPlayButton` function object.
|
|
78
|
+
* esbuild/tsx (via `keepNames`) rewrites the inner arrows to `__name(() => …)`;
|
|
79
|
+
* Puppeteer injects a function via `.toString()`, so that `__name` call would leak
|
|
80
|
+
* into the page and throw (it's undefined there). Wrapping in a closure that
|
|
81
|
+
* defines an `__name` shim makes the snippet bundler-proof.
|
|
82
|
+
*/
|
|
83
|
+
export function playButtonScript(message, id, binding) {
|
|
84
|
+
const args = [message, id, binding].map((a) => JSON.stringify(a)).join(",");
|
|
85
|
+
return `(() => { const __name = (f) => f; (${injectPlayButton.toString()})(${args}); })()`;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=play-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-button.js","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAEjD;;;GAGG;AACH,SAAS,gBAAgB,CACvB,OAAe,EACf,EAAU,EACV,OAAe;IAEf,wDAAwD;IACxD,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO;IACrC,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAAE,OAAO;QACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG;YACnB,gBAAgB;YAChB,UAAU;YACV,2EAA2E;YAC3E,iEAAiE;YACjE,UAAU;YACV,4BAA4B;YAC5B,oBAAoB;YACpB,qBAAqB;YACrB,cAAc;YACd,oBAAoB;YACpB,UAAU;YACV,6BAA6B;YAC7B,gCAAgC;YAChC,YAAY;YACZ,qBAAqB;YACrB,uCAAuC;YACvC,yDAAyD;SAC1D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG;YAClB,WAAW;YACX,gBAAgB;YAChB,qBAAqB;YACrB,oBAAoB;YACpB,wBAAwB;YACxB,YAAY;YACZ,aAAa;YACb,mBAAmB;YACnB,oBAAoB;YACpB,YAAY;YACZ,gBAAgB;SACjB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC;QACtB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC7C,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;QAC5B,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,gEAAgE,CAAC;QACnE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,KAAK;gBAAE,OAAO;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,EAAE,GAAI,MAAgD,CAAC,OAAO,CAAC,CAAC;YACtE,IAAI,OAAO,EAAE,KAAK,UAAU;gBAAE,EAAE,EAAE,CAAC;QACrC,CAAC,CAAC;QACF,sEAAsE;QACtE,iEAAiE;QACjE,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,IAAI;QAAE,KAAK,EAAE,CAAC;;QACtB,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,EAAU,EACV,OAAe;IAEf,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,OAAO,sCAAsC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,IAAI,SAAS,CAAC;AAC7F,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Page, type GoToOptions } from "puppeteer";
|
|
2
|
+
import type { ResolvedConfig, WaitForOptions } from "../config.js";
|
|
3
|
+
import { type Logger } from "../logger.js";
|
|
4
|
+
import { type ZoomState } from "./cursor.js";
|
|
5
|
+
export declare class Runtime {
|
|
6
|
+
private readonly getCfg;
|
|
7
|
+
private readonly log;
|
|
8
|
+
private readonly cursor;
|
|
9
|
+
private zoom;
|
|
10
|
+
private playResolver;
|
|
11
|
+
private playBindingExposed;
|
|
12
|
+
constructor(getCfg: () => ResolvedConfig, log: Logger);
|
|
13
|
+
/** The current zoom transform — the session re-reads it when re-injecting the
|
|
14
|
+
* cursor overlay across navigations. */
|
|
15
|
+
get zoomState(): ZoomState;
|
|
16
|
+
/** Drop any zoom transform (a fresh document carries none). */
|
|
17
|
+
resetZoomState(): void;
|
|
18
|
+
/** (Re-)inject the cursor overlay at the carried position, if enabled. */
|
|
19
|
+
injectCursor(page: Page): Promise<void>;
|
|
20
|
+
visit(page: Page, url: string, options?: GoToOptions): Promise<void>;
|
|
21
|
+
waitFor(page: Page, target: string, options?: WaitForOptions): Promise<void>;
|
|
22
|
+
click(page: Page, target: string): Promise<void>;
|
|
23
|
+
hover(page: Page, target: string): Promise<void>;
|
|
24
|
+
type(page: Page, target: string, text: string, options?: {
|
|
25
|
+
duration?: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
clear(page: Page, target: string): Promise<void>;
|
|
28
|
+
select(page: Page, target: string, value: string): Promise<void>;
|
|
29
|
+
key(page: Page, key: string): Promise<void>;
|
|
30
|
+
mouse(page: Page, target: string | {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
scroll(page: Page, target: string | number, options?: {
|
|
35
|
+
duration?: number;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
zoomTo(page: Page, level: number, options?: {
|
|
38
|
+
origin?: string;
|
|
39
|
+
duration?: number;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
resetZoom(page: Page, options?: {
|
|
42
|
+
duration?: number;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/** Click a target's centre. Uses page.mouse.click() not ElementHandle.click()
|
|
45
|
+
* to avoid Puppeteer's built-in scrollIntoView overriding our centred scroll. */
|
|
46
|
+
private _click;
|
|
47
|
+
/**
|
|
48
|
+
* Click at viewport coords and, if the click triggers a navigation, wait for the
|
|
49
|
+
* new page to settle before returning — so a navigating `click()` behaves like a
|
|
50
|
+
* `visit()` and the next action lands on the loaded page rather than racing it.
|
|
51
|
+
* In-page clicks pay only a short probe.
|
|
52
|
+
*/
|
|
53
|
+
private _clickPoint;
|
|
54
|
+
/** Move to viewport coords, animating the overlay when the cursor is enabled. */
|
|
55
|
+
private _moveTo;
|
|
56
|
+
/** Scroll a target into view using the configured margin/speed. */
|
|
57
|
+
private _scrollIntoView;
|
|
58
|
+
/**
|
|
59
|
+
* Block until the user resumes — by clicking the in-page ▶ Play button, or by
|
|
60
|
+
* pressing Enter in the *terminal*. The in-page button is click-only so the live
|
|
61
|
+
* page keeps its own keyboard (Enter to submit a form, etc.); the terminal
|
|
62
|
+
* fallback means a resume is still reachable if the button fails to render.
|
|
63
|
+
*/
|
|
64
|
+
waitForPlay(page: Page, message: string): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=runtime.d.ts.map
|