* Chore: Add move most types from rrweb to @rrweb/types package * Split off type imports * Split off type import to its own line * Get vite to generate type definitions * Apply formatting changes * noEmit not allowed in tsconfig, moved it to build step * Migrate rrdom-nodejs build to vite * Apply formatting changes * Migrate rrweb-snapshot to vite * Unify configs * Chore: Migrate rrdom to vite Turns out what we where doing by overwriting `public textContent: string | undefined` as a getter in a subclass is something that isn't allowed in typescript. Because we where using `// @ts-ignore` to hide this error our bundler chose to allow the overwrite. Vite choses to disallow the overwrite making all subclasses' `textContent` undefined. To mitigate this we're using an abstract class, which does allow sub classes to decide if they wan't to use getters or not. * Chore: Migrate rrweb to vite WIP * build:browser was removed (for now) * BREAKING: moved rrweb-plugin-console to its own npm module This removes console from rrweb-all.js * Support cjs files in startServer * Move canvas-webrtc plugin to its own package * Chore: move sequential-id plugin to its own package * Chore: Configure rrweb's vite bundling * `Id` had lowercase `d` before, making it lowercase again * Test: Move console tests to their own package * remove unused utils from rrdom * pull in latest version of master something when wrong earlier when resolving merge conflicts, this should be correct * Fix type casting issue in diff.ts * Fix typo * Fix duplicate entries in package.json and tsconfig.json * Apply formatting changes * Update dependencies in package.json files * Update dependencies to use Vite 5.2.8 in package.json files * Get tests passing for rrdom `apply virtual style rules to node` tests need to be moved to rrweb to avoid circular dependencies * Fix image loading issue in integration tests * Move pack/unpack to its own @rrweb/packer module * Get tests to work in rrdom-nodejs * Port tests in rrweb-snapshot to vitest and fix them * Fix tests for rrweb-plugin-console-record * Add @rrweb/all package * Fix publint and attw errors for rrdom and @rrweb/types * Use shared vitest.config.ts in rrweb-snapshot package * Fix publint and attw issues for rrweb-snapshot * Export `ReplayPlugin` type directly from rrweb * Fix publint and attw issues for packages * Fix publint & attw issue. I was bumping into this issue:3729bc2a3c/docs/problems/NoResolution.mdAnd had to choose one of these three methods described here: https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility And I ended up going for the method described here:1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)The redirect method seemed the least invasive and most effective. * Fix publint & attw issue. I was bumping into this issue:3729bc2a3c/docs/problems/NoResolution.mdAnd had to choose one of these three methods described here: https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility And I ended up going for the method described here:1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)The redirect method seemed the least invasive and most effective. * move some rrdom tests that require rrweb to rrweb package * Use pre-jest 29 syntax for snapshotting * get rrweb passing publint and attw * const enum does not work with isolated modules flag * Fix script tag type in webgl.test.ts.snap and update rrweb.umd.cjs path in webgl.test.ts * Fix paths * Move tests for console record plugin and fix bundle path * Fix tests for rrweb * pack integration tests were moved to @rrweb/all * Update rrweb bundle path in test files * Fix flaky scroll emit from test * Migrate rrweb's tests over to vitest and make them pass * Make sure benchmarks & updating tests work * Remove jest from rrweb * Fix paths * always use rrweb's own cssom * Update tsconfig.json for rrweb-plugin-sequential-id-record Fixes this error: Error: @rrweb/rrweb-plugin-sequential-id-record:prepublish: tsconfig.json(9,5): error TS6377: Cannot write file '/home/runner/work/rrweb/rrweb/tsconfig.tsbuildinfo' because it will overwrite '.tsbuildinfo' file generated by referenced project '/home/runner/work/rrweb/rrweb/packages/rrweb' * Add tsbuildinfo config to extended tsconfig files * Move rrdom over to vitest * Apply formatting changes * Update rrweb imports to use the new package structure * extend rrweb-snapshot's tsconfig from monorepo base config * extend @rrweb/types's tsconfig from monorepo base config * extend rrdom's tsconfig from monorepo base config * extend rrdom-nodejs's tsconfig from monorepo base config * extend web-extension's tsconfig from monorepo base config * unify tsconfigs * Continue when tests fail * Add stricter type checking * Add check-types global command * remove jest * Remove unused code * Add check-types command to build script * Fix linting issues * Add setup Chrome action for CI/CD workflow * Update puppeteer version in package.json for rrweb * Update Chrome setup in CI/CD workflow * Update Chrome setup in CI/CD workflow * Add Chrome setup and test cache location * Update CI/CD workflow to test chrome cache location * Add chrome installation step to CI/CD workflow * Update Puppeteer configuration for headless testing * Update dependencies and workflow configuration * Use same version of chrome on CI as is run locally * Use version of chrome that seems to work with rrdom tests * Try using puppeteerrc to define chrome version * Add .cache directory to .gitignore * Move global flag to vitest config * Update puppeteer version to 20.9.0 * Update console log messages in rrweb-plugin-console-record for new puppeteer version * Remove redundant Chrome setup from CI/CD workflow * Add minification and umd for all built files * Update import paths for rrweb dist files * Add @rrweb/replay and @rrweb/record * Add script to lint packages * Apply formatting changes * exclude styles export from typescript package type checking * WIP Move rrweb-player over to vite * Apply formatting changes * chore: Update rrweb plugin import paths * Remove rollup from rrweb-player * Fix typing issues * Fix typing issues * chore: Update rrweb-player to use vite for build process * Apply formatting changes * chore: Export Player class in rrweb-player/src/main.ts Makes attw happy * Apply formatting changes * Gets wiped by yarn workspaces-to-typescript-project-references * Add .eslintignore and .eslintrc.cjs files for rrweb-player package * Apply formatting changes * Update dependencies in rrweb-player/package.json * Apply formatting changes * chore: Update eslint configuration for rrweb-player package * Apply formatting changes * chore: Remove unused files from rrweb-player package * Apply formatting changes * chore: Update rrweb-player import path to use rrweb-player.cjs * chore: Update addEventListener signature in rrweb-player * Apply formatting changes * Add .eslintignore and update .gitignore files for to root * Apply formatting changes * Update documentation * Update @rrweb/types package description * Apply formatting changes * Update build and run commands in CONTRIBUTING.md * Apply formatting changes * Update package versions to 2.0.0-alpha.13 * Apply formatting changes * Apply formatting changes * Fix import statement in media/index.ts * Apply formatting changes * chore: Update .gitignore to exclude build and dist directories * Apply formatting changes * Apply formatting changes * Migrate setTimeout to vitest * Apply formatting changes * Apply formatting changes * Fix isNativeShadowDom function signature in utils.ts * try out jsr * Apply formatting changes * Update package versions to 2.0.0-alpha.14 * Apply formatting changes * Fix name of rrwebSnapshot object * Apply formatting changes * Remove unused lock files * Apply formatting changes * Update rrweb bundle path to use umd.cjs format * Apply formatting changes * Trigger tests to run again * Rename snapshots for vitest * Apply formatting changes * Ping CI * Apply formatting changes * Ping CI * Apply formatting changes * Ignore files generated by svelte-kit for prettier * Correct Player object
178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
import * as fs from 'fs-extra';
|
|
import * as path from 'path';
|
|
import { chromium } from 'playwright';
|
|
import { EventType, eventWithTime } from '@rrweb/types';
|
|
import type Player from 'rrweb-player';
|
|
|
|
const rrwebScriptPath = path.resolve(
|
|
require.resolve('rrweb-player'),
|
|
'../../dist/rrweb-player.umd.cjs',
|
|
);
|
|
const rrwebStylePath = path.resolve(rrwebScriptPath, '../style.css');
|
|
const rrwebRaw = fs.readFileSync(rrwebScriptPath, 'utf-8');
|
|
const rrwebStyle = fs.readFileSync(rrwebStylePath, 'utf-8');
|
|
// The max valid scale value for the scaling method which can improve the video quality.
|
|
const MaxScaleValue = 2.5;
|
|
|
|
type RRvideoConfig = {
|
|
input: string;
|
|
output?: string;
|
|
headless?: boolean;
|
|
// A number between 0 and 1. The higher the value, the better the quality of the video.
|
|
resolutionRatio?: number;
|
|
// A callback function that will be called when the progress of the replay is updated.
|
|
onProgressUpdate?: (percent: number) => void;
|
|
rrwebPlayer?: Omit<
|
|
ConstructorParameters<typeof Player>[0]['props'],
|
|
'events'
|
|
>;
|
|
};
|
|
|
|
const defaultConfig: Required<RRvideoConfig> = {
|
|
input: '',
|
|
output: 'rrvideo-output.webm',
|
|
headless: true,
|
|
// A good trade-off value between quality and file size.
|
|
resolutionRatio: 0.8,
|
|
onProgressUpdate: () => {
|
|
//
|
|
},
|
|
rrwebPlayer: {},
|
|
};
|
|
|
|
function getHtml(events: Array<eventWithTime>, config?: RRvideoConfig): string {
|
|
return `
|
|
<html>
|
|
<head>
|
|
<style>${rrwebStyle}</style>
|
|
<style>html, body {padding: 0; border: none; margin: 0;}</style>
|
|
</head>
|
|
<body>
|
|
<script>
|
|
${rrwebRaw};
|
|
/*<!--*/
|
|
const events = ${JSON.stringify(events).replace(
|
|
/<\/script>/g,
|
|
'<\\/script>',
|
|
)};
|
|
/*-->*/
|
|
const userConfig = ${JSON.stringify(config?.rrwebPlayer || {})};
|
|
window.replayer = new rrwebPlayer.Player({
|
|
target: document.body,
|
|
width: userConfig.width,
|
|
height: userConfig.height,
|
|
props: {
|
|
...userConfig,
|
|
events,
|
|
showController: false,
|
|
},
|
|
});
|
|
window.replayer.addEventListener('finish', () => window.onReplayFinish());
|
|
window.replayer.addEventListener('ui-update-progress', (payload)=> window.onReplayProgressUpdate
|
|
(payload));
|
|
window.replayer.addEventListener('resize',()=>document.querySelector('.replayer-wrapper').style.transform = 'scale(${
|
|
(config?.resolutionRatio ?? 1) * MaxScaleValue
|
|
}) translate(-50%, -50%)');
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Preprocess all events to get a maximum view port size.
|
|
*/
|
|
function getMaxViewport(events: eventWithTime[]) {
|
|
let maxWidth = 0,
|
|
maxHeight = 0;
|
|
events.forEach((event) => {
|
|
if (event.type !== EventType.Meta) return;
|
|
if (event.data.width > maxWidth) maxWidth = event.data.width;
|
|
if (event.data.height > maxHeight) maxHeight = event.data.height;
|
|
});
|
|
return {
|
|
width: maxWidth,
|
|
height: maxHeight,
|
|
};
|
|
}
|
|
|
|
export async function transformToVideo(options: RRvideoConfig) {
|
|
const defaultVideoDir = '__rrvideo__temp__';
|
|
const config = { ...defaultConfig };
|
|
if (!options.input) throw new Error('input is required');
|
|
// If the output is not specified or undefined, use the default value.
|
|
if (!options.output) delete options.output;
|
|
Object.assign(config, options);
|
|
if (config.resolutionRatio > 1) config.resolutionRatio = 1; // The max value is 1.
|
|
|
|
const eventsPath = path.isAbsolute(config.input)
|
|
? config.input
|
|
: path.resolve(process.cwd(), config.input);
|
|
const outputPath = path.isAbsolute(config.output)
|
|
? config.output
|
|
: path.resolve(process.cwd(), config.output);
|
|
const events = JSON.parse(
|
|
fs.readFileSync(eventsPath, 'utf-8'),
|
|
) as eventWithTime[];
|
|
|
|
// Make the browser viewport fit the player size.
|
|
const maxViewport = getMaxViewport(events);
|
|
// Use the scaling method to improve the video quality.
|
|
const scaledViewport = {
|
|
width: Math.round(
|
|
maxViewport.width * (config.resolutionRatio ?? 1) * MaxScaleValue,
|
|
),
|
|
height: Math.round(
|
|
maxViewport.height * (config.resolutionRatio ?? 1) * MaxScaleValue,
|
|
),
|
|
};
|
|
Object.assign(config.rrwebPlayer, scaledViewport);
|
|
const browser = await chromium.launch({
|
|
headless: config.headless,
|
|
});
|
|
const context = await browser.newContext({
|
|
viewport: scaledViewport,
|
|
recordVideo: {
|
|
dir: defaultVideoDir,
|
|
size: scaledViewport,
|
|
},
|
|
});
|
|
const page = await context.newPage();
|
|
await page.goto('about:blank');
|
|
await page.exposeFunction(
|
|
'onReplayProgressUpdate',
|
|
(data: { payload: number }) => {
|
|
config.onProgressUpdate(data.payload);
|
|
},
|
|
);
|
|
|
|
// Wait for the replay to finish
|
|
await new Promise<void>(
|
|
(resolve) =>
|
|
void page
|
|
.exposeFunction('onReplayFinish', () => resolve())
|
|
.then(() => page.setContent(getHtml(events, config))),
|
|
);
|
|
const videoPath = (await page.video()?.path()) || '';
|
|
const cleanFiles = async (videoPath: string) => {
|
|
await fs.remove(videoPath);
|
|
if ((await fs.readdir(defaultVideoDir)).length === 0) {
|
|
await fs.remove(defaultVideoDir);
|
|
}
|
|
};
|
|
await context.close();
|
|
await Promise.all([
|
|
fs
|
|
.move(videoPath, outputPath, { overwrite: true })
|
|
.catch((e) => {
|
|
console.error(
|
|
"Can't create video file. Please check the output path.",
|
|
e,
|
|
);
|
|
})
|
|
.finally(() => void cleanFiles(videoPath)),
|
|
browser.close(),
|
|
]);
|
|
return outputPath;
|
|
}
|