Add .gitignore to exclude all node packages and lock files

This commit is contained in:
Adolfo Reyna
2026-02-23 21:56:04 -05:00
parent faae96c9ed
commit dcc5c6c044
9747 changed files with 1555105 additions and 2 deletions
@@ -0,0 +1,10 @@
import type { CancelSignal } from '../make-cancel-signal';
export declare const applyToneFrequencyUsingFfmpeg: ({ input, output, toneFrequency, indent, logLevel, binariesDirectory, cancelSignal, }: {
input: string;
output: string;
toneFrequency: number;
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
cancelSignal: CancelSignal | undefined;
}) => Promise<void>;
@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyToneFrequencyUsingFfmpeg = void 0;
const call_ffmpeg_1 = require("../call-ffmpeg");
const logger_1 = require("../logger");
const sample_rate_1 = require("../sample-rate");
const applyToneFrequencyUsingFfmpeg = async ({ input, output, toneFrequency, indent, logLevel, binariesDirectory, cancelSignal, }) => {
const filter = `asetrate=${sample_rate_1.DEFAULT_SAMPLE_RATE}*${toneFrequency},aresample=${sample_rate_1.DEFAULT_SAMPLE_RATE},atempo=1/${toneFrequency}`;
const args = [
'-hide_banner',
'-i',
input,
['-ac', '2'],
'-filter:a',
filter,
['-c:a', 'pcm_s16le'],
['-ar', String(sample_rate_1.DEFAULT_SAMPLE_RATE)],
'-y',
output,
].flat(2);
logger_1.Log.verbose({ indent, logLevel }, 'Changing tone frequency using FFmpeg:', JSON.stringify(args.join(' ')), 'Filter:', filter);
const startTimestamp = Date.now();
const task = (0, call_ffmpeg_1.callFf)({
bin: 'ffmpeg',
args,
indent,
logLevel,
binariesDirectory,
cancelSignal,
});
await task;
logger_1.Log.verbose({ indent, logLevel }, 'Changed tone frequency using FFmpeg', `${Date.now() - startTimestamp}ms`);
};
exports.applyToneFrequencyUsingFfmpeg = applyToneFrequencyUsingFfmpeg;
@@ -0,0 +1,3 @@
import type { AudioOrVideoAsset } from 'remotion/no-react';
import type { Assets } from './types';
export declare const calculateAssetPositions: (frames: AudioOrVideoAsset[][]) => Assets;
@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateAssetPositions = void 0;
const filter_asset_types_1 = require("../filter-asset-types");
const resolve_asset_src_1 = require("../resolve-asset-src");
const flatten_volume_array_1 = require("./flatten-volume-array");
const types_1 = require("./types");
const areEqual = (a, b) => {
return a.id === b.id;
};
const findFrom = (target, renderAsset) => {
const index = target.findIndex((a) => areEqual(a, renderAsset));
if (index === -1) {
return false;
}
target.splice(index, 1);
return true;
};
const copyAndDeduplicateAssets = (renderAssets) => {
const onlyAudioAndVideo = (0, filter_asset_types_1.onlyAudioAndVideoAssets)(renderAssets);
const deduplicated = [];
for (const renderAsset of onlyAudioAndVideo) {
if (!deduplicated.find((d) => d.id === renderAsset.id)) {
deduplicated.push(renderAsset);
}
}
return deduplicated;
};
const calculateAssetPositions = (frames) => {
var _a, _b;
const assets = [];
const flattened = frames.flat(1);
for (let frame = 0; frame < frames.length; frame++) {
const prev = copyAndDeduplicateAssets((_a = frames[frame - 1]) !== null && _a !== void 0 ? _a : []);
const current = copyAndDeduplicateAssets(frames[frame]);
const next = copyAndDeduplicateAssets((_b = frames[frame + 1]) !== null && _b !== void 0 ? _b : []);
for (const asset of current) {
if (!findFrom(prev, asset)) {
assets.push({
src: (0, resolve_asset_src_1.resolveAssetSrc)((0, types_1.uncompressMediaAsset)(flattened, asset).src),
type: asset.type,
duration: null,
id: asset.id,
startInVideo: frame,
trimLeft: asset.mediaFrame,
volume: [],
playbackRate: asset.playbackRate,
toneFrequency: asset.toneFrequency,
audioStartFrame: asset.audioStartFrame,
audioStreamIndex: asset.audioStreamIndex,
});
}
const found = assets.find((a) => a.duration === null && areEqual(a, asset));
if (!found)
throw new Error('something wrong');
if (!findFrom(next, asset)) {
// Duration calculation:
// start 0, range 0-59:
// 59 - 0 + 1 ==> 60 frames duration
found.duration = frame - found.startInVideo + 1;
}
found.volume = [...found.volume, asset.volume];
}
}
for (const asset of assets) {
if (asset.duration === null) {
throw new Error('duration is unexpectedly null');
}
}
return assets.map((a) => (0, flatten_volume_array_1.convertAssetToFlattenedVolume)(a));
};
exports.calculateAssetPositions = calculateAssetPositions;
@@ -0,0 +1 @@
export declare const calculateATempo: (playbackRate: number) => string | null;
@@ -0,0 +1,16 @@
"use strict";
// Solving this problem of FFMPEG:
// https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video
// "The atempo filter is limited to using values between 0.5 and 2.0 (so it can slow it down to no less than half the original speed, and speed up to no more than double the input). If you need to, you can get around this limitation by stringing multiple atempo filters together. The following with quadruple the audio speed:"
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateATempo = void 0;
const calculateATempo = (playbackRate) => {
if (playbackRate === 1) {
return null;
}
if (playbackRate >= 0.5 && playbackRate <= 2) {
return `atempo=${playbackRate.toFixed(5)}`;
}
return `${(0, exports.calculateATempo)(Math.sqrt(playbackRate))},${(0, exports.calculateATempo)(Math.sqrt(playbackRate))}`;
};
exports.calculateATempo = calculateATempo;
@@ -0,0 +1,12 @@
import type { AudioOrVideoAsset } from 'remotion/no-react';
import type { FrameAndAssets } from '../render-frames';
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
import type { DownloadMap } from './download-map';
export declare const convertAssetsToFileUrls: ({ assets, onDownload, downloadMap, indent, logLevel, binariesDirectory, }: {
assets: FrameAndAssets[];
onDownload: RenderMediaOnDownload;
downloadMap: DownloadMap;
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
}) => Promise<AudioOrVideoAsset[][]>;
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertAssetsToFileUrls = void 0;
const download_and_map_assets_to_file_1 = require("./download-and-map-assets-to-file");
const chunk = (input, size) => {
return input.reduce((arr, item, idx) => {
return idx % size === 0
? [...arr, [item]]
: [...arr.slice(0, -1), [...arr.slice(-1)[0], item]];
}, []);
};
const convertAssetsToFileUrls = async ({ assets, onDownload, downloadMap, indent, logLevel, binariesDirectory, }) => {
const chunks = chunk(assets, 1000);
const results = [];
for (const ch of chunks) {
const assetPromises = ch.map((frame) => {
const frameAssetPromises = frame.audioAndVideoAssets.map((a) => {
return (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
renderAsset: a,
onDownload,
downloadMap,
indent,
logLevel,
binariesDirectory,
cancelSignalForAudioAnalysis: undefined,
shouldAnalyzeAudioImmediately: true,
});
});
return Promise.all(frameAssetPromises);
});
const result = await Promise.all(assetPromises);
results.push(result);
}
return results.flat(1);
};
exports.convertAssetsToFileUrls = convertAssetsToFileUrls;
@@ -0,0 +1,36 @@
import type { AudioOrVideoAsset } from 'remotion/no-react';
import type { CancelSignal } from '../make-cancel-signal';
import type { DownloadMap } from './download-map';
export type RenderMediaOnDownload = (src: string) => ((progress: {
percent: number | null;
downloaded: number;
totalSize: number | null;
}) => void) | undefined | void;
export declare const downloadAsset: ({ src, downloadMap, indent, logLevel, shouldAnalyzeAudioImmediately, binariesDirectory, cancelSignalForAudioAnalysis, audioStreamIndex, }: {
src: string;
downloadMap: DownloadMap;
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
shouldAnalyzeAudioImmediately: boolean;
binariesDirectory: string | null;
cancelSignalForAudioAnalysis: CancelSignal | undefined;
audioStreamIndex: number | undefined;
}) => Promise<string>;
export declare const markAllAssetsAsDownloaded: (downloadMap: DownloadMap) => void;
export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, contentDisposition, contentType, }: {
src: string;
downloadDir: string;
contentDisposition: string | null;
contentType: string | null;
}) => string;
export declare const downloadAndMapAssetsToFileUrl: ({ renderAsset, onDownload, downloadMap, logLevel, indent, binariesDirectory, cancelSignalForAudioAnalysis, shouldAnalyzeAudioImmediately, }: {
renderAsset: AudioOrVideoAsset;
onDownload: RenderMediaOnDownload | null;
downloadMap: DownloadMap;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
indent: boolean;
shouldAnalyzeAudioImmediately: boolean;
binariesDirectory: string | null;
cancelSignalForAudioAnalysis: CancelSignal | undefined;
}) => Promise<AudioOrVideoAsset>;
export declare const attachDownloadListenerToEmitter: (downloadMap: DownloadMap, onDownload: RenderMediaOnDownload | null) => () => void;
@@ -0,0 +1,313 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.attachDownloadListenerToEmitter = exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = exports.downloadAsset = void 0;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importStar(require("node:path"));
const no_react_1 = require("remotion/no-react");
const compress_assets_1 = require("../compress-assets");
const ensure_output_directory_1 = require("../ensure-output-directory");
const mime_types_1 = require("../mime-types");
const download_file_1 = require("./download-file");
const get_audio_channels_1 = require("./get-audio-channels");
const sanitize_filepath_1 = require("./sanitize-filepath");
const waitForAssetToBeDownloaded = ({ src, downloadDir, downloadMap, }) => {
var _a, _b;
if ((_a = downloadMap.hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[downloadDir]) {
return Promise.resolve((_b = downloadMap.hasBeenDownloadedMap[src]) === null || _b === void 0 ? void 0 : _b[downloadDir]);
}
if (!downloadMap.listeners[src]) {
downloadMap.listeners[src] = {};
}
if (!downloadMap.listeners[src][downloadDir]) {
downloadMap.listeners[src][downloadDir] = [];
}
return new Promise((resolve) => {
downloadMap.listeners[src][downloadDir].push(() => {
const srcMap = downloadMap.hasBeenDownloadedMap[src];
if (!(srcMap === null || srcMap === void 0 ? void 0 : srcMap[downloadDir])) {
throw new Error('Expected file for ' + src + 'to be available in ' + downloadDir);
}
resolve(srcMap[downloadDir]);
});
});
};
const notifyAssetIsDownloaded = ({ src, downloadDir, to, downloadMap, }) => {
if (!downloadMap.listeners[src]) {
downloadMap.listeners[src] = {};
}
if (!downloadMap.listeners[src][downloadDir]) {
downloadMap.listeners[src][downloadDir] = [];
}
if (!downloadMap.isDownloadingMap[src]) {
downloadMap.isDownloadingMap[src] = {};
}
downloadMap.isDownloadingMap[src][downloadDir] = false;
if (!downloadMap.hasBeenDownloadedMap[src]) {
downloadMap.hasBeenDownloadedMap[src] = {};
}
downloadMap.hasBeenDownloadedMap[src][downloadDir] = to;
downloadMap.listeners[src][downloadDir].forEach((fn) => fn());
};
const validateMimeType = (mimeType, src) => {
if (!mimeType.includes('/')) {
const errMessage = [
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
'The `mime-type` parameter must be a valid mime type.',
'The data that was received is (truncated to 100 characters):',
src.substr(0, 100),
].join(' ');
throw new TypeError(errMessage);
}
};
function validateBufferEncoding(potentialEncoding, dataUrl) {
const asserted = potentialEncoding;
const validEncodings = [
'ascii',
'base64',
'base64url',
'binary',
'hex',
'latin1',
'ucs-2',
'ucs2',
'utf-8',
'utf16le',
'utf8',
];
if (!validEncodings.find((en) => asserted === en)) {
const errMessage = [
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
'The `encoding` parameter must be one of the following:',
`${validEncodings.join(' ')}.`,
'The data that was received is (truncated to 100 characters):',
dataUrl.substr(0, 100),
].join(' ');
throw new TypeError(errMessage);
}
}
const downloadAsset = async ({ src, downloadMap, indent, logLevel, shouldAnalyzeAudioImmediately, binariesDirectory, cancelSignalForAudioAnalysis, audioStreamIndex, }) => {
var _a, _b, _c;
if ((0, compress_assets_1.isAssetCompressed)(src)) {
return src;
}
const { downloadDir } = downloadMap;
if ((_a = downloadMap.hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[downloadDir]) {
const claimedDownloadLocation = (_b = downloadMap.hasBeenDownloadedMap[src]) === null || _b === void 0 ? void 0 : _b[downloadDir];
// The OS might have deleted the file since even though we marked it as downloaded. In that case we reset the state and download it again
if (node_fs_1.default.existsSync(claimedDownloadLocation)) {
return claimedDownloadLocation;
}
downloadMap.hasBeenDownloadedMap[src][downloadDir] = null;
if (!downloadMap.isDownloadingMap[src]) {
downloadMap.isDownloadingMap[src] = {};
}
downloadMap.isDownloadingMap[src][downloadDir] = false;
}
if ((_c = downloadMap.isDownloadingMap[src]) === null || _c === void 0 ? void 0 : _c[downloadDir]) {
return waitForAssetToBeDownloaded({ downloadMap, src, downloadDir });
}
if (!downloadMap.isDownloadingMap[src]) {
downloadMap.isDownloadingMap[src] = {};
}
downloadMap.isDownloadingMap[src][downloadDir] = true;
downloadMap.emitter.dispatchDownload(src);
if (src.startsWith('data:')) {
const [assetDetails, assetData] = src.substring('data:'.length).split(',');
if (!assetDetails.includes(';')) {
const errMessage = [
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
'The data that was received is (truncated to 100 characters):',
src.substring(0, 100),
].join(' ');
throw new TypeError(errMessage);
}
const [mimeType, encoding] = assetDetails.split(';');
validateMimeType(mimeType, src);
validateBufferEncoding(encoding, src);
const output = (0, exports.getSanitizedFilenameForAssetUrl)({
contentDisposition: null,
downloadDir,
src,
contentType: mimeType,
});
(0, ensure_output_directory_1.ensureOutputDirectory)(output);
const buff = Buffer.from(assetData, encoding);
await node_fs_1.default.promises.writeFile(output, buff);
notifyAssetIsDownloaded({ src, downloadMap, downloadDir, to: output });
return output;
}
const { to } = await (0, download_file_1.downloadFile)({
url: src,
onProgress: (progress) => {
downloadMap.emitter.dispatchDownloadProgress(src, progress.percent, progress.downloaded, progress.totalSize);
},
to: (contentDisposition, contentType) => (0, exports.getSanitizedFilenameForAssetUrl)({
contentDisposition,
downloadDir,
src,
contentType,
}),
indent,
logLevel,
abortSignal: downloadMap.cleanupController.signal,
});
notifyAssetIsDownloaded({ src, downloadMap, downloadDir, to });
if (shouldAnalyzeAudioImmediately) {
await (0, get_audio_channels_1.getAudioChannelsAndDuration)({
binariesDirectory,
downloadMap,
src: to,
indent,
logLevel,
cancelSignal: cancelSignalForAudioAnalysis,
audioStreamIndex,
});
}
return to;
};
exports.downloadAsset = downloadAsset;
const markAllAssetsAsDownloaded = (downloadMap) => {
Object.keys(downloadMap.hasBeenDownloadedMap).forEach((key) => {
delete downloadMap.hasBeenDownloadedMap[key];
});
Object.keys(downloadMap.isDownloadingMap).forEach((key) => {
delete downloadMap.isDownloadingMap[key];
});
};
exports.markAllAssetsAsDownloaded = markAllAssetsAsDownloaded;
const getFilename = ({ contentDisposition, src, contentType, }) => {
const filenameProbe = 'filename=';
if (contentDisposition === null || contentDisposition === void 0 ? void 0 : contentDisposition.includes(filenameProbe)) {
const start = contentDisposition.indexOf(filenameProbe);
const onlyFromFileName = contentDisposition.substring(start + filenameProbe.length);
const hasSemi = onlyFromFileName.indexOf(';');
if (hasSemi === -1) {
return { pathname: onlyFromFileName.trim(), search: '' };
}
return {
search: '',
pathname: onlyFromFileName.substring(0, hasSemi).trim(),
};
}
const { pathname, search } = new URL(src);
const ext = (0, node_path_1.extname)(pathname);
// Has no file extension, check if we can derive it from contentType
if (!ext && contentType) {
const matchedExt = (0, mime_types_1.getExt)(contentType);
return {
pathname: `${pathname}.${matchedExt}`,
search,
};
}
return { pathname, search };
};
const getSanitizedFilenameForAssetUrl = ({ src, downloadDir, contentDisposition, contentType, }) => {
if ((0, compress_assets_1.isAssetCompressed)(src)) {
return src;
}
const { pathname, search } = getFilename({
contentDisposition,
contentType,
src,
});
const split = pathname.split('.');
const fileExtension = split.length > 1 && split[split.length - 1]
? `.${split[split.length - 1]}`
: '';
const hashedFileName = String((0, no_react_1.random)(`${src}${pathname}${search}`)).replace('0.', '');
const filename = hashedFileName + fileExtension;
return node_path_1.default.join(downloadDir, (0, sanitize_filepath_1.sanitizeFilePath)(filename));
};
exports.getSanitizedFilenameForAssetUrl = getSanitizedFilenameForAssetUrl;
const downloadAndMapAssetsToFileUrl = async ({ renderAsset, onDownload, downloadMap, logLevel, indent, binariesDirectory, cancelSignalForAudioAnalysis, shouldAnalyzeAudioImmediately, }) => {
const cleanup = (0, exports.attachDownloadListenerToEmitter)(downloadMap, onDownload);
const newSrc = await (0, exports.downloadAsset)({
src: renderAsset.src,
downloadMap,
indent,
logLevel,
shouldAnalyzeAudioImmediately,
binariesDirectory,
cancelSignalForAudioAnalysis,
audioStreamIndex: renderAsset.audioStreamIndex,
});
cleanup();
return {
...renderAsset,
src: newSrc,
};
};
exports.downloadAndMapAssetsToFileUrl = downloadAndMapAssetsToFileUrl;
const attachDownloadListenerToEmitter = (downloadMap, onDownload) => {
const cleanup = [];
if (!onDownload) {
return () => undefined;
}
if (downloadMap.downloadListeners.includes(onDownload)) {
return () => undefined;
}
downloadMap.downloadListeners.push(onDownload);
cleanup.push(() => {
downloadMap.downloadListeners = downloadMap.downloadListeners.filter((l) => l !== onDownload);
return Promise.resolve();
});
const cleanupDownloadListener = downloadMap.emitter.addEventListener('download', ({ detail: { src: initialSrc } }) => {
const progress = onDownload(initialSrc);
const cleanupProgressListener = downloadMap.emitter.addEventListener('progress', ({ detail: { downloaded, percent, src: progressSrc, totalSize } }) => {
if (initialSrc === progressSrc) {
progress === null || progress === void 0 ? void 0 : progress({ downloaded, percent, totalSize });
}
});
cleanup.push(() => {
cleanupProgressListener();
return Promise.resolve();
});
});
cleanup.push(() => {
cleanupDownloadListener();
return Promise.resolve();
});
return () => {
cleanup.forEach((c) => c());
};
};
exports.attachDownloadListenerToEmitter = attachDownloadListenerToEmitter;
@@ -0,0 +1,19 @@
import type { LogLevel } from '../log-level';
type Response = {
sizeInBytes: number;
to: string;
};
type Options = {
url: string;
to: (contentDisposition: string | null, contentType: string | null) => string;
onProgress: ((progress: {
percent: number | null;
downloaded: number;
totalSize: number | null;
}) => void) | undefined;
logLevel: LogLevel;
indent: boolean;
abortSignal: AbortSignal;
};
export declare const downloadFile: (options: Options, retries?: number, attempt?: number) => Promise<Response>;
export {};
@@ -0,0 +1,150 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadFile = void 0;
const node_fs_1 = require("node:fs");
const ensure_output_directory_1 = require("../ensure-output-directory");
const logger_1 = require("../logger");
const read_file_1 = require("./read-file");
const CANCELLED_ERROR = 'cancelled';
const incorrectContentLengthToken = 'Download finished with';
const downloadFileWithoutRetries = ({ onProgress, url, to: toFn, abortSignal, }) => {
return new Promise((resolve, reject) => {
let rejected = false;
let resolved = false;
let timeout;
const resolveAndFlag = (val) => {
abortSignal.removeEventListener('abort', onAbort);
resolved = true;
resolve(val);
if (timeout) {
clearTimeout(timeout);
}
};
const rejectAndFlag = (err) => {
abortSignal.removeEventListener('abort', onAbort);
if (timeout) {
clearTimeout(timeout);
}
reject(err);
rejected = true;
};
const refreshTimeout = () => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
if (resolved) {
return;
}
rejectAndFlag(new Error(`Tried to download file ${url}, but the server sent no data for 20 seconds`));
}, 20000);
};
refreshTimeout();
let finishEventSent = false;
let closeConnection = () => undefined;
const onAbort = () => {
rejectAndFlag(new Error(CANCELLED_ERROR));
closeConnection();
};
abortSignal.addEventListener('abort', onAbort);
(0, read_file_1.readFile)(url)
.then(({ response, request }) => {
var _a, _b;
closeConnection = () => {
request.destroy();
response.destroy();
};
if (abortSignal.aborted) {
onAbort();
return;
}
const contentDisposition = (_a = response.headers['content-disposition']) !== null && _a !== void 0 ? _a : null;
const contentType = (_b = response.headers['content-type']) !== null && _b !== void 0 ? _b : null;
const to = toFn(contentDisposition, contentType);
(0, ensure_output_directory_1.ensureOutputDirectory)(to);
const sizeHeader = response.headers['content-length'];
const totalSize = typeof sizeHeader === 'undefined' ? null : Number(sizeHeader);
const writeStream = (0, node_fs_1.createWriteStream)(to);
let downloaded = 0;
// Listen to 'close' event instead of more
// concise method to avoid this problem
// https://github.com/remotion-dev/remotion/issues/384#issuecomment-844398183
writeStream.on('close', () => {
if (rejected) {
return;
}
if (!finishEventSent) {
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
downloaded,
percent: 1,
totalSize: downloaded,
});
}
refreshTimeout();
return resolveAndFlag({ sizeInBytes: downloaded, to });
});
writeStream.on('error', (err) => rejectAndFlag(err));
response.on('error', (err) => {
closeConnection();
rejectAndFlag(err);
});
response.pipe(writeStream).on('error', (err) => rejectAndFlag(err));
response.on('data', (d) => {
refreshTimeout();
downloaded += d.length;
refreshTimeout();
const percent = totalSize === null ? null : downloaded / totalSize;
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
downloaded,
percent,
totalSize,
});
if (percent === 1) {
finishEventSent = true;
}
});
response.on('close', () => {
if (totalSize !== null && downloaded !== totalSize) {
rejectAndFlag(new Error(`${incorrectContentLengthToken} ${downloaded} bytes, but expected ${totalSize} bytes from 'Content-Length'.`));
}
writeStream.close();
closeConnection();
});
})
.catch((err) => {
rejectAndFlag(err);
});
});
};
const downloadFile = async (options, retries = 2, attempt = 1) => {
try {
const res = await downloadFileWithoutRetries(options);
return res;
}
catch (err) {
const { message } = err;
if (message === CANCELLED_ERROR) {
throw err;
}
if (message === 'aborted' ||
message.includes('ECONNRESET') ||
message.includes(incorrectContentLengthToken) ||
// Try again if hitting internal errors
message.includes('503') ||
message.includes('502') ||
message.includes('504') ||
message.includes('500')) {
if (retries === 0) {
throw err;
}
logger_1.Log.warn({ indent: options.indent, logLevel: options.logLevel }, `Downloading ${options.url} failed (will retry): ${message}`);
const backoffInSeconds = (attempt + 1) ** 2;
await new Promise((resolve) => {
setTimeout(() => resolve(), backoffInSeconds * 1000);
});
return (0, exports.downloadFile)(options, retries - 1, attempt + 1);
}
throw err;
}
};
exports.downloadFile = downloadFile;
@@ -0,0 +1,56 @@
import { OffthreadVideoServerEmitter } from '../offthread-video-server';
import type { FrameAndAssets } from '../render-frames';
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
import { type InlineAudioMixing } from './inline-audio-mixing';
export type AudioChannelsAndDurationResultCache = {
channels: number;
duration: number | null;
startTime: number | null;
};
export type DownloadMap = {
id: string;
emitter: OffthreadVideoServerEmitter;
downloadListeners: RenderMediaOnDownload[];
isDownloadingMap: {
[src: string]: {
[downloadDir: string]: boolean;
} | undefined;
};
hasBeenDownloadedMap: {
[src: string]: {
[downloadDir: string]: string | null;
} | undefined;
};
listeners: {
[key: string]: {
[downloadDir: string]: (() => void)[];
};
};
durationOfAssetCache: Record<string, AudioChannelsAndDurationResultCache>;
downloadDir: string;
preEncode: string;
audioMixing: string;
complexFilter: string;
audioPreprocessing: string;
stitchFrames: string;
assetDir: string;
compositingDir: string;
preventCleanup: () => void;
allowCleanup: () => void;
isPreventedFromCleanup: () => boolean;
inlineAudioMixing: InlineAudioMixing;
cleanupController: AbortController;
};
export type RenderAssetInfo = {
assets: FrameAndAssets[];
imageSequenceName: string;
firstFrameIndex: number;
downloadMap: DownloadMap;
chunkLengthInSeconds: number;
trimLeftOffset: number;
trimRightOffset: number;
forSeamlessAacConcatenation: boolean;
};
export declare const makeAndReturn: (dir: string, name: string) => string;
export declare const makeDownloadMap: () => DownloadMap;
export declare const cleanDownloadMap: (downloadMap: DownloadMap) => void;
@@ -0,0 +1,65 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanDownloadMap = exports.makeDownloadMap = exports.makeAndReturn = void 0;
const node_fs_1 = require("node:fs");
const node_path_1 = __importDefault(require("node:path"));
const version_1 = require("remotion/version");
const delete_directory_1 = require("../delete-directory");
const offthread_video_server_1 = require("../offthread-video-server");
const tmp_dir_1 = require("../tmp-dir");
const inline_audio_mixing_1 = require("./inline-audio-mixing");
const makeAndReturn = (dir, name) => {
const p = node_path_1.default.join(dir, name);
(0, node_fs_1.mkdirSync)(p);
return p;
};
exports.makeAndReturn = makeAndReturn;
const makeDownloadMap = () => {
const dir = (0, tmp_dir_1.tmpDir)(`remotion-v${version_1.VERSION}-assets`);
let prevented = false;
return {
isDownloadingMap: {},
hasBeenDownloadedMap: {},
listeners: {},
durationOfAssetCache: {},
id: String(Math.random()),
assetDir: dir,
downloadListeners: [],
downloadDir: (0, exports.makeAndReturn)(dir, 'remotion-assets-dir'),
complexFilter: (0, exports.makeAndReturn)(dir, 'remotion-complex-filter'),
preEncode: (0, exports.makeAndReturn)(dir, 'pre-encode'),
audioMixing: (0, exports.makeAndReturn)(dir, 'remotion-audio-mixing'),
audioPreprocessing: (0, exports.makeAndReturn)(dir, 'remotion-audio-preprocessing'),
stitchFrames: (0, exports.makeAndReturn)(dir, 'remotion-stitch-temp-dir'),
compositingDir: (0, exports.makeAndReturn)(dir, 'remotion-compositing-temp-dir'),
emitter: new offthread_video_server_1.OffthreadVideoServerEmitter(),
preventCleanup: () => {
prevented = true;
},
allowCleanup: () => {
prevented = false;
},
isPreventedFromCleanup: () => {
return prevented;
},
inlineAudioMixing: (0, inline_audio_mixing_1.makeInlineAudioMixing)(dir),
cleanupController: new AbortController(),
};
};
exports.makeDownloadMap = makeDownloadMap;
const cleanDownloadMap = (downloadMap) => {
if (downloadMap.isPreventedFromCleanup()) {
return;
}
downloadMap.cleanupController.abort();
(0, delete_directory_1.deleteDirectory)(downloadMap.downloadDir);
(0, delete_directory_1.deleteDirectory)(downloadMap.complexFilter);
(0, delete_directory_1.deleteDirectory)(downloadMap.compositingDir);
downloadMap.inlineAudioMixing.cleanup();
// Assets dir must be last since the others are contained
(0, delete_directory_1.deleteDirectory)(downloadMap.assetDir);
};
exports.cleanDownloadMap = cleanDownloadMap;
@@ -0,0 +1,12 @@
import type { AssetVolume } from './types';
type FfmpegEval = 'once' | 'frame';
type FfmpegVolumeExpression = {
eval: FfmpegEval;
value: string;
};
export declare const ffmpegVolumeExpression: ({ volume, fps, trimLeft, }: {
volume: AssetVolume;
trimLeft: number;
fps: number;
}) => FfmpegVolumeExpression;
export {};
@@ -0,0 +1,96 @@
"use strict";
// Example is here https://ffmpeg.org/ffmpeg-filters.html#volume
// Allowed syntax is here: https://ffmpeg.org/ffmpeg-utils.html#Expression-Evaluation
Object.defineProperty(exports, "__esModule", { value: true });
exports.ffmpegVolumeExpression = void 0;
// If once, ffmpeg evaluates volume expression once.
// If frame, it evaluates it for each frame
const round_volume_to_avoid_stack_overflow_1 = require("./round-volume-to-avoid-stack-overflow");
// In FFMPEG expressions, the current time is represented by 't'
// the `n` for the timestamp variable is buggy
const FFMPEG_TIME_VARIABLE = 't';
const ffmpegIfOrElse = (condition, then, elseDo) => {
return `if(${condition},${then},${elseDo})`;
};
const ffmpegIsOneOfFrames = ({ frames, trimLeft, fps, }) => {
const consecutiveArrays = [];
for (let i = 0; i < frames.length; i++) {
const previousFrame = frames[i - 1];
const frame = frames[i];
if (previousFrame === undefined || frame !== previousFrame + 1) {
consecutiveArrays.push([]);
}
consecutiveArrays[consecutiveArrays.length - 1].push(frame);
}
return consecutiveArrays
.map((f) => {
const firstFrame = f[0];
const lastFrame = f[f.length - 1];
const before = (firstFrame - 0.5) / fps;
const after = (lastFrame + 0.5) / fps;
return `between(${FFMPEG_TIME_VARIABLE},${(before + trimLeft).toFixed(4)},${(after + trimLeft).toFixed(4)})`;
})
.join('+');
};
const ffmpegBuildVolumeExpression = ({ arr, delay, fps, }) => {
if (arr.length === 0) {
throw new Error('Volume array expression should never have length 0');
}
if (arr.length === 1) {
return String(arr[0][0]);
}
const [first, ...rest] = arr;
const [volume, frames] = first;
return ffmpegIfOrElse(ffmpegIsOneOfFrames({ frames, trimLeft: delay, fps }), String(volume), ffmpegBuildVolumeExpression({ arr: rest, delay, fps }));
};
const ffmpegVolumeExpression = ({ volume, fps, trimLeft, }) => {
// If it's a static volume, we return it and tell
// FFMPEG it only has to evaluate it once
if (typeof volume === 'number') {
return {
eval: 'once',
value: String(volume),
};
}
if ([...new Set(volume)].length === 1) {
return (0, exports.ffmpegVolumeExpression)({
volume: volume[0],
fps,
trimLeft,
});
}
// A 1 sec video with frames 0-29 would mean that
// frame 29 corresponds to timestamp 0.966666...
// but the audio is actually 1 sec long. For that reason we pad the last
// timestamp.
const paddedVolume = [...volume, volume[volume.length - 1]];
// Otherwise, we construct an FFMPEG expression. First step:
// Make a map of all possible volumes
// {possibleVolume1} => [frame1, frame2]
// {possibleVolume2} => [frame3, frame4]
const volumeMap = {};
paddedVolume.forEach((baseVolume, frame) => {
// Adjust volume based on how many other tracks have not yet finished
const actualVolume = (0, round_volume_to_avoid_stack_overflow_1.roundVolumeToAvoidStackOverflow)(baseVolume);
if (!volumeMap[actualVolume]) {
volumeMap[actualVolume] = [];
}
volumeMap[actualVolume].push(frame);
});
// Sort the map so that the most common volume is last
// this is going to be the else statement so the expression is short
const volumeArray = Object.keys(volumeMap)
.map((key) => [Number(key), volumeMap[key]])
.sort((a, b) => a[1].length - b[1].length);
// Construct and tell FFMPEG it has to evaluate expression on each frame
const expression = ffmpegBuildVolumeExpression({
arr: volumeArray,
delay: trimLeft,
fps,
});
return {
eval: 'frame',
value: `'${expression}'`,
};
};
exports.ffmpegVolumeExpression = ffmpegVolumeExpression;
@@ -0,0 +1,3 @@
import type { AssetVolume, MediaAsset } from './types';
export declare const flattenVolumeArray: (volume: AssetVolume) => AssetVolume;
export declare const convertAssetToFlattenedVolume: (asset: MediaAsset) => MediaAsset;
@@ -0,0 +1,26 @@
"use strict";
// An input is an array with volume levels for each frame, like
// [1, 1, 1, 1, 1, ...].
// If all elements are the same, we flatten it to `1`, otherwise we leave it as an array.
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertAssetToFlattenedVolume = exports.flattenVolumeArray = void 0;
const flattenVolumeArray = (volume) => {
if (typeof volume === 'number') {
return volume;
}
if (volume.length === 0) {
throw new TypeError('Volume array must have at least 1 number');
}
if (new Set(volume).size === 1) {
return volume[0];
}
return volume;
};
exports.flattenVolumeArray = flattenVolumeArray;
const convertAssetToFlattenedVolume = (asset) => {
return {
...asset,
volume: (0, exports.flattenVolumeArray)(asset.volume),
};
};
exports.convertAssetToFlattenedVolume = convertAssetToFlattenedVolume;
@@ -0,0 +1,19 @@
import type { CancelSignal } from '../make-cancel-signal';
import type { AudioChannelsAndDurationResultCache, DownloadMap } from './download-map';
export declare const getAudioChannelsAndDurationWithoutCache: ({ src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }: {
src: string;
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
cancelSignal: CancelSignal | undefined;
audioStreamIndex: number | undefined;
}) => Promise<AudioChannelsAndDurationResultCache>;
export declare const getAudioChannelsAndDuration: ({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }: {
downloadMap: DownloadMap;
src: string;
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
cancelSignal: CancelSignal | undefined;
audioStreamIndex: number | undefined;
}) => Promise<AudioChannelsAndDurationResultCache>;
@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAudioChannelsAndDuration = exports.getAudioChannelsAndDurationWithoutCache = void 0;
const call_ffmpeg_1 = require("../call-ffmpeg");
const p_limit_1 = require("../p-limit");
const limit = (0, p_limit_1.pLimit)(1);
const getAudioChannelsAndDurationWithoutCache = async ({ src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) => {
const args = [
['-v', 'error'],
['-select_streams', audioStreamIndex ? `a:${audioStreamIndex}` : 'a:0'],
[
'-show_entries',
'stream=channels:stream=start_time:format=duration:format=format_name',
],
['-of', 'default=nw=1'],
[src],
]
.reduce((acc, val) => acc.concat(val), [])
.filter(Boolean);
try {
const task = await (0, call_ffmpeg_1.callFf)({
bin: 'ffprobe',
args,
indent,
logLevel,
binariesDirectory,
cancelSignal,
});
const channels = task.stdout.match(/channels=([0-9]+)/);
const duration = task.stdout.match(/duration=([0-9.]+)/);
const startTime = task.stdout.match(/start_time=([0-9.]+)/);
const container = task.stdout.match(/format_name=([a-zA-Z0-9.]+)/);
const isMP3 = container ? container[1] === 'mp3' : false;
const result = {
channels: channels ? parseInt(channels[1], 10) : 0,
duration: duration ? parseFloat(duration[1]) : null,
// We ignore the start time for MP3 because that is an inherent encoder thing
// not in the sense that we want
startTime: startTime ? (isMP3 ? 0 : parseFloat(startTime[1])) : null,
};
return result;
}
catch (err) {
if (err.message.includes('This file cannot be read by `ffprobe`. Is it a valid multimedia file?')) {
throw new Error('This file cannot be read by `ffprobe`. Is it a valid multimedia file?');
}
throw err;
}
};
exports.getAudioChannelsAndDurationWithoutCache = getAudioChannelsAndDurationWithoutCache;
async function getAudioChannelsAndDurationUnlimited({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) {
const cacheKey = audioStreamIndex ? `${src}-${audioStreamIndex}` : src;
if (downloadMap.durationOfAssetCache[cacheKey]) {
return downloadMap.durationOfAssetCache[cacheKey];
}
const result = await (0, exports.getAudioChannelsAndDurationWithoutCache)({
src,
indent,
logLevel,
binariesDirectory,
cancelSignal,
audioStreamIndex,
});
downloadMap.durationOfAssetCache[cacheKey] = result;
return result;
}
const getAudioChannelsAndDuration = ({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) => {
return limit(() => getAudioChannelsAndDurationUnlimited({
downloadMap,
src,
indent,
logLevel,
binariesDirectory,
cancelSignal,
audioStreamIndex,
}));
};
exports.getAudioChannelsAndDuration = getAudioChannelsAndDuration;
@@ -0,0 +1,21 @@
import type { InlineAudioAsset } from 'remotion/no-react';
import type { CancelSignal } from '../make-cancel-signal';
export declare const makeInlineAudioMixing: (dir: string) => {
cleanup: () => void;
addAsset: ({ asset, fps, totalNumberOfFrames, firstFrame, trimLeftOffset, trimRightOffset, }: {
asset: InlineAudioAsset;
fps: number;
totalNumberOfFrames: number;
firstFrame: number;
trimLeftOffset: number;
trimRightOffset: number;
}) => void;
getListOfAssets: () => string[];
finish: ({ binariesDirectory, indent, logLevel, cancelSignal, }: {
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
cancelSignal: CancelSignal | undefined;
}) => Promise<void>;
};
export type InlineAudioMixing = ReturnType<typeof makeInlineAudioMixing>;
@@ -0,0 +1,200 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeInlineAudioMixing = void 0;
const node_fs_1 = __importStar(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const delete_directory_1 = require("../delete-directory");
const sample_rate_1 = require("../sample-rate");
const apply_tone_frequency_1 = require("./apply-tone-frequency");
const download_map_1 = require("./download-map");
const numberTo32BiIntLittleEndian = (num) => {
return new Uint8Array([
num & 0xff,
(num >> 8) & 0xff,
(num >> 16) & 0xff,
(num >> 24) & 0xff,
]);
};
const numberTo16BitLittleEndian = (num) => {
return new Uint8Array([num & 0xff, (num >> 8) & 0xff]);
};
/**
* When multiplying seconds by sample rate, floating point errors can cause
* results like 244799.99999999997 instead of 244800 (5.1 * 48000).
* This snaps to the nearest integer when within tolerance,
* preventing Math.floor/Math.ceil from rounding incorrectly.
*/
const correctFloatingPointError = (value) => {
const rounded = Math.round(value);
if (Math.abs(value - rounded) < 0.00001) {
return rounded;
}
return value;
};
const BIT_DEPTH = 16;
const BYTES_PER_SAMPLE = BIT_DEPTH / 8;
const NUMBER_OF_CHANNELS = 2;
const makeInlineAudioMixing = (dir) => {
const folderToAdd = (0, download_map_1.makeAndReturn)(dir, 'remotion-inline-audio-mixing');
// asset id -> file descriptor
const openFiles = {};
const writtenHeaders = {};
const toneFrequencies = {};
const cleanup = () => {
for (const fd of Object.values(openFiles)) {
try {
node_fs_1.default.closeSync(fd);
}
catch (_a) { }
}
(0, delete_directory_1.deleteDirectory)(folderToAdd);
};
const getListOfAssets = () => {
return Object.keys(openFiles);
};
const getFilePath = (asset) => {
return node_path_1.default.join(folderToAdd, `${asset.id}.wav`);
};
const ensureAsset = ({ asset, fps, totalNumberOfFrames, trimLeftOffset, trimRightOffset, }) => {
const filePath = getFilePath(asset);
if (!openFiles[filePath]) {
openFiles[filePath] = node_fs_1.default.openSync(filePath, 'w');
}
if (writtenHeaders[filePath]) {
return;
}
writtenHeaders[filePath] = true;
const expectedDataSize = Math.round((totalNumberOfFrames / fps - trimLeftOffset + trimRightOffset) *
NUMBER_OF_CHANNELS *
sample_rate_1.DEFAULT_SAMPLE_RATE *
BYTES_PER_SAMPLE);
const expectedSize = 40 + expectedDataSize;
const fd = openFiles[filePath];
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x52, 0x49, 0x46, 0x46]), 0, 4, 0); // "RIFF"
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedSize)), 0, 4, 4); // Remaining size
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x57, 0x41, 0x56, 0x45]), 0, 4, 8); // "WAVE"
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x66, 0x6d, 0x74, 0x20]), 0, 4, 12); // "fmt "
(0, node_fs_1.writeSync)(fd, new Uint8Array([BIT_DEPTH, 0x00, 0x00, 0x00]), 0, 4, 16); // fmt chunk size = 16
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x01, 0x00]), 0, 2, 20); // Audio format (PCM) = 1, set 3 if float32 would be true
(0, node_fs_1.writeSync)(fd, new Uint8Array([NUMBER_OF_CHANNELS, 0x00]), 0, 2, 22); // Number of channels
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(sample_rate_1.DEFAULT_SAMPLE_RATE)), 0, 4, 24); // Sample rate
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(sample_rate_1.DEFAULT_SAMPLE_RATE * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 4, 28); // Byte rate
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo16BitLittleEndian(NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 2, 32); // Block align
(0, node_fs_1.writeSync)(fd, numberTo16BitLittleEndian(BIT_DEPTH), 0, 2, 34); // Bits per sample
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x64, 0x61, 0x74, 0x61]), 0, 4, 36); // "data"
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedDataSize)), 0, 4, 40); // Remaining size
};
const finish = async ({ binariesDirectory, indent, logLevel, cancelSignal, }) => {
for (const fd of Object.keys(openFiles)) {
const frequency = toneFrequencies[fd];
if (frequency !== 1) {
const tmpFile = fd.replace(/.wav$/, '-tmp.wav');
await (0, apply_tone_frequency_1.applyToneFrequencyUsingFfmpeg)({
input: fd,
output: tmpFile,
toneFrequency: frequency,
indent,
logLevel,
binariesDirectory,
cancelSignal,
});
node_fs_1.default.renameSync(tmpFile, fd);
}
}
};
const addAsset = ({ asset, fps, totalNumberOfFrames, firstFrame, trimLeftOffset, trimRightOffset, }) => {
ensureAsset({
asset,
fps,
totalNumberOfFrames,
trimLeftOffset,
trimRightOffset,
});
const filePath = getFilePath(asset);
if (toneFrequencies[filePath] !== undefined &&
toneFrequencies[filePath] !== asset.toneFrequency) {
throw new Error(`toneFrequency must be the same across the entire audio, got ${asset.toneFrequency}, but before it was ${toneFrequencies[filePath]}`);
}
const fileDescriptor = openFiles[filePath];
toneFrequencies[filePath] = asset.toneFrequency;
let arr = new Int16Array(asset.audio);
const isFirst = asset.frame === firstFrame;
const isLast = asset.frame === totalNumberOfFrames + firstFrame - 1;
const samplesToShaveFromStart = trimLeftOffset * sample_rate_1.DEFAULT_SAMPLE_RATE;
const samplesToShaveFromEnd = trimRightOffset * sample_rate_1.DEFAULT_SAMPLE_RATE;
// Higher tolerance is needed for floating point videos
// Rendering https://github.com/remotion-dev/remotion/pull/5920 in native frame rate
// could hit this case
if (isFirst) {
arr = arr.slice(Math.floor(correctFloatingPointError(samplesToShaveFromStart)) *
NUMBER_OF_CHANNELS);
}
if (isLast) {
arr = arr.slice(0, arr.length +
Math.ceil(correctFloatingPointError(samplesToShaveFromEnd)) *
NUMBER_OF_CHANNELS);
}
const positionInSeconds = (asset.frame - firstFrame) / fps - (isFirst ? 0 : trimLeftOffset);
// Always rounding down to ensure there are no gaps when the samples don't align
// In @remotion/media, we also round down the sample start timestamp and round up the end timestamp
// This might lead to overlapping, hopefully aligning perfectly!
// Test case: https://github.com/remotion-dev/remotion/issues/5758
const position = Math.floor(correctFloatingPointError(positionInSeconds * sample_rate_1.DEFAULT_SAMPLE_RATE)) *
NUMBER_OF_CHANNELS *
BYTES_PER_SAMPLE;
(0, node_fs_1.writeSync)(
// fs
fileDescriptor,
// data
arr,
// offset of data
0,
// length
arr.byteLength,
// position
44 + position);
};
return {
cleanup,
addAsset,
getListOfAssets,
finish,
};
};
exports.makeInlineAudioMixing = makeInlineAudioMixing;
@@ -0,0 +1,7 @@
import http from 'node:http';
type NodeRequestAndResponse = {
request: http.ClientRequest;
response: http.IncomingMessage;
};
export declare const readFile: (url: string, redirectsSoFar?: number) => Promise<NodeRequestAndResponse>;
export {};
@@ -0,0 +1,97 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readFile = void 0;
const https_1 = __importDefault(require("https"));
const node_http_1 = __importDefault(require("node:http"));
const redirect_status_codes_1 = require("../redirect-status-codes");
const truthy_1 = require("../truthy");
const getClient = (url) => {
if (url.startsWith('https://')) {
return https_1.default.get;
}
if (url.startsWith('http://')) {
return node_http_1.default.get;
}
throw new Error(`Can only download URLs starting with http:// or https://, got "${url}"`);
};
const readFileWithoutRedirect = (url) => {
return new Promise((resolve, reject) => {
const client = getClient(url);
const req = client(url,
// Bun 1.1.16 does not support the `headers` option
typeof Bun === 'undefined'
? {
headers: {
'user-agent': 'Mozilla/5.0 (@remotion/renderer - https://remotion.dev)',
},
}
: {}, (res) => {
resolve({ request: req, response: res });
});
req.on('error', (err) => {
req.destroy();
return reject(err);
});
});
};
const readFile = async (url, redirectsSoFar = 0) => {
if (redirectsSoFar > 10) {
throw new Error(`Too many redirects while downloading ${url}`);
}
const { request, response } = await readFileWithoutRedirect(url);
if (redirect_status_codes_1.redirectStatusCodes.includes(response.statusCode)) {
if (!response.headers.location) {
throw new Error(`Received a status code ${response.statusCode} but no "Location" header while calling ${response.headers.location}`);
}
const { origin } = new URL(url);
const redirectUrl = new URL(response.headers.location, origin).toString();
request.destroy();
response.destroy();
return (0, exports.readFile)(redirectUrl, redirectsSoFar + 1);
}
if (response.statusCode >= 400) {
const body = await tryToObtainBody(response);
request.destroy();
response.destroy();
throw new Error([
`Received a status code of ${response.statusCode} while downloading file ${url}.`,
body ? `The response body was:` : null,
body ? `---` : null,
body ? body : null,
body ? `---` : null,
]
.filter(truthy_1.truthy)
.join('\n'));
}
return { request, response };
};
exports.readFile = readFile;
const tryToObtainBody = async (file) => {
const success = new Promise((resolve) => {
let data = '';
file.on('data', (chunk) => {
data += chunk;
});
file.on('end', () => {
resolve(data);
});
// OK even when getting an error, this is just a best effort
file.on('error', () => resolve(data));
});
let timeout = null;
const body = await Promise.race([
success,
new Promise((resolve) => {
timeout = setTimeout(() => {
resolve(null);
}, 5000);
}),
]);
if (timeout) {
clearTimeout(timeout);
}
return body;
};
@@ -0,0 +1 @@
export declare const roundVolumeToAvoidStackOverflow: (volume: number) => number;
@@ -0,0 +1,17 @@
"use strict";
// FFMPEG allows expressions to have a maximum depth of 100.
// As defined here: https://github.com/FFmpeg/FFmpeg/blob/2014b0135293c41d261757bfa1aaba51653bab8e/libavutil/eval.c#L706
// With one depth added by the if/else statement,
// this means we can have a maximum of 99 different volumes.
// Therefore we round the volumes (which can only be between 0 and 1)
// so that there are only 99 possible values.
// We then subtract 1 again because FFMPEG is not precise and queries out of range
// values, for which we have to provide a default
Object.defineProperty(exports, "__esModule", { value: true });
exports.roundVolumeToAvoidStackOverflow = void 0;
const MAX_FFMPEG_STACK_DEPTH = 98;
const roundVolumeToAvoidStackOverflow = (volume) => {
return Number((Math.round(volume * (MAX_FFMPEG_STACK_DEPTH - 1)) /
(MAX_FFMPEG_STACK_DEPTH - 1)).toFixed(3));
};
exports.roundVolumeToAvoidStackOverflow = roundVolumeToAvoidStackOverflow;
@@ -0,0 +1 @@
export declare const sanitizeFilename: (input: string) => string;
@@ -0,0 +1,58 @@
"use strict";
// Inlined from https://github.com/parshap/node-sanitize-filename/blob/master/index.js
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitizeFilename = void 0;
const truncate_utf8_bytes_1 = require("./truncate-utf8-bytes");
/**
* Replaces characters in strings that are illegal/unsafe for filenames.
* Unsafe characters are either removed or replaced by a substitute set
* in the optional `options` object.
*
* Illegal Characters on Various Operating Systems
* / ? < > \ : * | "
* https://kb.acronis.com/content/39790
*
* Unicode Control codes
* C0 0x00-0x1f & C1 (0x80-0x9f)
* http://en.wikipedia.org/wiki/C0_and_C1_control_codes
*
* Reserved filenames on Unix-based systems (".", "..")
* Reserved filenames in Windows ("CON", "PRN", "AUX", "NUL", "COM1",
* "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
* "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", and
* "LPT9") case-insesitively and with or without filename extensions.
*
* Capped at 255 characters in length.
* http://unix.stackexchange.com/questions/32795/what-is-the-maximum-allowed-filename-and-folder-size-with-ecryptfs
*
* @param {String} input Original filename
* @param {Object} options {replacement: String | Function }
* @return {String} Sanitized filename
*/
const illegalRe = /[/?<>\\:*|"]/g;
// eslint-disable-next-line no-control-regex
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
const reservedRe = /^\.+$/;
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
const windowsTrailingRe = /[. ]+$/;
function sanitize(input, replacement) {
if (typeof input !== 'string') {
throw new Error('Input must be string');
}
const sanitized = input
.replace(illegalRe, replacement)
.replace(controlRe, replacement)
.replace(reservedRe, replacement)
.replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement);
return (0, truncate_utf8_bytes_1.truncateUtf8Bytes)(sanitized, 255);
}
const sanitizeFilename = (input) => {
const replacement = '';
const output = sanitize(input, replacement);
if (replacement === '') {
return output;
}
return sanitize(output, '');
};
exports.sanitizeFilename = sanitizeFilename;
@@ -0,0 +1 @@
export declare const sanitizeFilePath: (pathToSanitize: string) => string;
@@ -0,0 +1,16 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitizeFilePath = void 0;
const node_path_1 = __importDefault(require("node:path"));
const sanitize_filename_1 = require("./sanitize-filename");
const pathSeparators = /[/\\]/;
const sanitizeFilePath = (pathToSanitize) => {
return pathToSanitize
.split(pathSeparators)
.map((s) => (0, sanitize_filename_1.sanitizeFilename)(s))
.join(node_path_1.default.sep);
};
exports.sanitizeFilePath = sanitizeFilePath;
@@ -0,0 +1 @@
export declare function truncateUtf8Bytes(string: string, byteLength: number): string;
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.truncateUtf8Bytes = truncateUtf8Bytes;
function isHighSurrogate(codePoint) {
return codePoint >= 0xd800 && codePoint <= 0xdbff;
}
function isLowSurrogate(codePoint) {
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
}
const getLength = Buffer.byteLength.bind(Buffer);
function truncateUtf8Bytes(string, byteLength) {
if (typeof string !== 'string') {
throw new Error('Input must be string');
}
const charLength = string.length;
let curByteLength = 0;
let codePoint;
let segment;
for (let i = 0; i < charLength; i += 1) {
codePoint = string.charCodeAt(i);
segment = string[i];
if (isHighSurrogate(codePoint) &&
isLowSurrogate(string.charCodeAt(i + 1))) {
i += 1;
segment += string[i];
}
curByteLength += getLength(segment);
if (curByteLength === byteLength) {
return string.slice(0, i + 1);
}
if (curByteLength > byteLength) {
return string.slice(0, i - segment.length + 1);
}
}
return string;
}
@@ -0,0 +1,18 @@
import type { AudioOrVideoAsset } from 'remotion/no-react';
export type UnsafeAsset = Omit<AudioOrVideoAsset, 'frame' | 'id' | 'volume' | 'mediaFrame' | 'audioStartFrom'> & {
startInVideo: number;
duration: number | null;
trimLeft: number;
volume: number[];
id: string;
playbackRate: number;
toneFrequency: number;
audioStreamIndex: number;
};
export type AssetVolume = number | number[];
export type MediaAsset = Omit<UnsafeAsset, 'duration' | 'volume'> & {
duration: number;
volume: AssetVolume;
};
export declare const uncompressMediaAsset: (allRenderAssets: AudioOrVideoAsset[], assetToUncompress: AudioOrVideoAsset) => AudioOrVideoAsset;
export type Assets = MediaAsset[];
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.uncompressMediaAsset = void 0;
const uncompressMediaAsset = (allRenderAssets, assetToUncompress) => {
const isCompressed = assetToUncompress.src.match(/same-as-(.*)-([0-9.]+)$/);
if (!isCompressed) {
return assetToUncompress;
}
const [, id, frame] = isCompressed;
const assetToFill = allRenderAssets.find((a) => a.id === id && String(a.frame) === frame);
if (!assetToFill) {
console.log('List of assets:');
console.log(allRenderAssets);
throw new TypeError(`Cannot uncompress asset, asset list seems corrupt. Please file a bug in the Remotion repo with the debug information above.`);
}
return {
...assetToUncompress,
src: assetToFill.src,
};
};
exports.uncompressMediaAsset = uncompressMediaAsset;
@@ -0,0 +1,3 @@
type BitrateUnit = 'k' | 'K' | 'M';
export type Bitrate = `${number}${BitrateUnit}`;
export {};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
export type BrowserExecutable = string | null;
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
import type { ConsoleMessageLocation, ConsoleMessageType } from './browser/ConsoleMessage';
export type BrowserLog = {
text: string;
stackTrace: ConsoleMessageLocation[];
type: ConsoleMessageType;
};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
export type Browser = 'chrome';
export declare const DEFAULT_BROWSER: Browser;
@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_BROWSER = void 0;
exports.DEFAULT_BROWSER = 'chrome';
@@ -0,0 +1,97 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BrowserLog } from '../browser-log';
import type { LogLevel } from '../log-level';
import type { OnLog, Page } from './BrowserPage';
import type { BrowserRunner } from './BrowserRunner';
import type { Connection } from './Connection';
import { EventEmitter } from './EventEmitter';
import type { Viewport } from './PuppeteerViewport';
import type { SourceMapGetter } from './source-map-getter';
import { Target } from './Target';
interface WaitForTargetOptions {
timeout?: number;
}
export declare const enum BrowserEmittedEvents {
TargetChanged = "targetchanged",
TargetCreated = "targetcreated",
Closed = "closed",
ClosedSilent = "closed-silent"
}
export declare class HeadlessBrowser extends EventEmitter {
#private;
static create({ defaultViewport, timeout, userDataDir, args, executablePath, logLevel, indent }: {
defaultViewport: Viewport;
timeout: number;
userDataDir: string;
args: string[];
executablePath: string;
logLevel: LogLevel;
indent: boolean;
}): Promise<HeadlessBrowser>;
connection: Connection;
id: string;
runner: BrowserRunner;
get _targets(): Map<string, Target>;
constructor({ connection, defaultViewport, runner }: {
connection: Connection;
defaultViewport: Viewport;
runner: BrowserRunner;
});
browserContexts(): BrowserContext[];
newPage({ context, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
context: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
}): Promise<Page>;
_createPageInContext({ context, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
context: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
}): Promise<Page>;
targets(): Target[];
waitForTarget(predicate: (x: Target) => boolean | Promise<boolean>, options?: WaitForTargetOptions): Promise<Target>;
pages(): Promise<Page[]>;
close({ silent }: {
silent: boolean;
}): Promise<void>;
disconnect(): void;
}
export declare class BrowserContext extends EventEmitter {
#private;
constructor(browser: HeadlessBrowser);
targets(): Target[];
waitForTarget(predicate: (x: Target) => boolean | Promise<boolean>, options?: {
timeout?: number;
}): Promise<Target>;
pages(): Promise<Page[]>;
newPage({ context, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
context: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
}): Promise<Page>;
browser(): HeadlessBrowser;
}
export {};
@@ -0,0 +1,237 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserContext = exports.HeadlessBrowser = void 0;
const assert_1 = require("./assert");
const BrowserRunner_1 = require("./BrowserRunner");
const EventEmitter_1 = require("./EventEmitter");
const Target_1 = require("./Target");
const util_1 = require("./util");
class HeadlessBrowser extends EventEmitter_1.EventEmitter {
static async create({ defaultViewport, timeout, userDataDir, args, executablePath, logLevel, indent, }) {
const runner = await (0, BrowserRunner_1.makeBrowserRunner)({
executablePath,
processArguments: args,
userDataDir,
indent,
logLevel,
timeout,
});
const browser = new HeadlessBrowser({
connection: runner.connection,
defaultViewport,
runner,
});
await runner.connection.send('Target.setDiscoverTargets', { discover: true });
return browser;
}
#defaultViewport;
connection;
#defaultContext;
#contexts;
#targets;
id;
runner;
get _targets() {
return this.#targets;
}
constructor({ connection, defaultViewport, runner, }) {
super();
this.#defaultViewport = defaultViewport;
this.connection = connection;
this.id = Math.random().toString(36).substring(2, 15);
this.#defaultContext = new BrowserContext(this);
this.#contexts = new Map();
this.#targets = new Map();
this.connection.on('Target.targetCreated', this.#targetCreated.bind(this));
this.connection.on('Target.targetDestroyed', this.#targetDestroyed.bind(this));
this.connection.on('Target.targetInfoChanged', this.#targetInfoChanged.bind(this));
this.runner = runner;
}
browserContexts() {
return [this.#defaultContext, ...Array.from(this.#contexts.values())];
}
async #targetCreated(event) {
var _a;
const { targetInfo } = event;
const { browserContextId } = targetInfo;
const context = browserContextId && this.#contexts.has(browserContextId)
? this.#contexts.get(browserContextId)
: this.#defaultContext;
if (!context) {
throw new Error('Missing browser context');
}
const target = new Target_1.Target(targetInfo, context, () => {
return this.connection.createSession(targetInfo);
}, (_a = this.#defaultViewport) !== null && _a !== void 0 ? _a : null);
(0, assert_1.assert)(!this.#targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this.#targets.set(event.targetInfo.targetId, target);
if (await target._initializedPromise) {
this.emit("targetcreated" /* BrowserEmittedEvents.TargetCreated */, target);
}
}
#targetDestroyed(event) {
const target = this.#targets.get(event.targetId);
if (!target) {
throw new Error(`Missing target in _targetDestroyed (id = ${event.targetId})`);
}
target._initializedCallback(false);
this.#targets.delete(event.targetId);
target._closedCallback();
}
#targetInfoChanged(event) {
const target = this.#targets.get(event.targetInfo.targetId);
if (!target) {
throw new Error(`Missing target in targetInfoChanged (id = ${event.targetInfo.targetId})`);
}
const previousURL = target.url();
const wasInitialized = target._isInitialized;
target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url()) {
this.emit("targetchanged" /* BrowserEmittedEvents.TargetChanged */, target);
}
}
newPage({ context, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
return this.#defaultContext.newPage({
context,
logLevel,
indent,
pageIndex,
onBrowserLog,
onLog,
});
}
async _createPageInContext({ context, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
const { value: { targetId }, } = await this.connection.send('Target.createTarget', {
url: 'about:blank',
browserContextId: undefined,
});
const target = this.#targets.get(targetId);
if (!target) {
throw new Error(`Missing target for page (id = ${targetId})`);
}
const initialized = await target._initializedPromise;
if (!initialized) {
throw new Error(`Failed to create target for page (id = ${targetId})`);
}
const page = await target.page({
sourceMapGetter: context,
logLevel,
indent,
pageIndex,
onBrowserLog,
onLog,
});
if (!page) {
throw new Error(`Failed to create a page for context`);
}
return page;
}
targets() {
return Array.from(this.#targets.values()).filter((target) => {
return target._isInitialized;
});
}
async waitForTarget(predicate, options = {}) {
const { timeout = 30000 } = options;
let resolve;
let isResolved = false;
const targetPromise = new Promise((x) => {
resolve = x;
});
this.on("targetcreated" /* BrowserEmittedEvents.TargetCreated */, check);
this.on("targetchanged" /* BrowserEmittedEvents.TargetChanged */, check);
try {
if (!timeout) {
return await targetPromise;
}
this.targets().forEach(check);
return await (0, util_1.waitWithTimeout)(targetPromise, 'target', timeout, this);
}
finally {
this.off("targetcreated" /* BrowserEmittedEvents.TargetCreated */, check);
this.off("targetchanged" /* BrowserEmittedEvents.TargetChanged */, check);
}
async function check(target) {
if ((await predicate(target)) && !isResolved) {
isResolved = true;
resolve(target);
}
}
}
async pages() {
const contextPages = await Promise.all(this.browserContexts().map((context) => {
return context.pages();
}));
// Flatten array.
return contextPages.reduce((acc, x) => {
return acc.concat(x);
}, []);
}
async close({ silent }) {
await this.runner.closeProcess();
(await this.pages()).forEach((page) => {
page.emit("disposed" /* PageEmittedEvents.Disposed */);
page.closed = true;
});
this.disconnect();
this.emit(silent ? "closed-silent" /* BrowserEmittedEvents.ClosedSilent */ : "closed" /* BrowserEmittedEvents.Closed */);
}
disconnect() {
this.connection.dispose();
}
}
exports.HeadlessBrowser = HeadlessBrowser;
class BrowserContext extends EventEmitter_1.EventEmitter {
#browser;
constructor(browser) {
super();
this.#browser = browser;
}
targets() {
return this.#browser.targets().filter((target) => {
return target.browserContext() === this;
});
}
waitForTarget(predicate, options = {}) {
return this.#browser.waitForTarget((target) => {
return target.browserContext() === this && predicate(target);
}, options);
}
async pages() {
const pages = await Promise.all(this.targets()
.filter((target) => target.type() === 'page')
.map((target) => target.expectPage()));
return pages.filter((page) => {
return Boolean(page);
});
}
newPage({ context, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
return this.#browser._createPageInContext({
context,
logLevel,
indent,
pageIndex,
onBrowserLog,
onLog,
});
}
browser() {
return this.#browser;
}
}
exports.BrowserContext = BrowserContext;
@@ -0,0 +1,33 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DownloadBrowserProgressFn } from '../options/on-browser-download';
import { TESTED_VERSION } from './get-chrome-download-url';
export { TESTED_VERSION };
interface BrowserFetcherRevisionInfo {
folderPath: string;
executablePath: string;
url: string;
local: boolean;
}
export declare const readVersionFile: (chromeMode: "chrome-for-testing" | "headless-shell") => string | null;
export declare const downloadBrowser: ({ logLevel, indent, onProgress, version, chromeMode, }: {
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
indent: boolean;
onProgress: DownloadBrowserProgressFn;
version: string | null;
chromeMode: "chrome-for-testing" | "headless-shell";
}) => Promise<BrowserFetcherRevisionInfo | undefined>;
export declare const getRevisionInfo: (chromeMode: "chrome-for-testing" | "headless-shell") => BrowserFetcherRevisionInfo;
@@ -0,0 +1,246 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRevisionInfo = exports.downloadBrowser = exports.readVersionFile = exports.TESTED_VERSION = void 0;
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const extract_zip_1 = __importDefault(require("extract-zip"));
const node_util_1 = require("node:util");
const download_file_1 = require("../assets/download-file");
const make_file_executable_1 = require("../compositor/make-file-executable");
const get_chrome_download_url_1 = require("./get-chrome-download-url");
Object.defineProperty(exports, "TESTED_VERSION", { enumerable: true, get: function () { return get_chrome_download_url_1.TESTED_VERSION; } });
const get_download_destination_1 = require("./get-download-destination");
const mkdirAsync = fs.promises.mkdir;
const unlinkAsync = (0, node_util_1.promisify)(fs.unlink.bind(fs));
function existsAsync(filePath) {
return new Promise((resolve) => {
fs.access(filePath, (err) => {
return resolve(!err);
});
});
}
const getPlatform = () => {
const platform = os.platform();
switch (platform) {
case 'darwin':
return os.arch() === 'arm64' ? 'mac-arm64' : 'mac-x64';
case 'linux':
return os.arch() === 'arm64' ? 'linux-arm64' : 'linux64';
case 'win32':
return 'win64';
default:
throw new Error('Unsupported platform: ' + platform);
}
};
const getDownloadsFolder = (chromeMode) => {
const destination = chromeMode === 'headless-shell'
? 'chrome-headless-shell'
: 'chrome-for-testing';
return path.join((0, get_download_destination_1.getDownloadsCacheDir)(), destination);
};
const getVersionFilePath = (chromeMode) => {
const downloadsFolder = getDownloadsFolder(chromeMode);
return path.join(downloadsFolder, 'VERSION');
};
const getExpectedVersion = (version, _chromeMode) => {
if (version) {
return version;
}
return get_chrome_download_url_1.TESTED_VERSION;
};
const readVersionFile = (chromeMode) => {
const versionFilePath = getVersionFilePath(chromeMode);
try {
return fs.readFileSync(versionFilePath, 'utf-8').trim();
}
catch (_a) {
return null;
}
};
exports.readVersionFile = readVersionFile;
const writeVersionFile = (chromeMode, version) => {
const versionFilePath = getVersionFilePath(chromeMode);
fs.writeFileSync(versionFilePath, version);
};
const downloadBrowser = async ({ logLevel, indent, onProgress, version, chromeMode, }) => {
const platform = getPlatform();
const downloadURL = (0, get_chrome_download_url_1.getChromeDownloadUrl)({ platform, version, chromeMode });
const fileName = downloadURL.split('/').pop();
if (!fileName) {
throw new Error(`A malformed download URL was found: ${downloadURL}.`);
}
const downloadsFolder = getDownloadsFolder(chromeMode);
const archivePath = path.join(downloadsFolder, fileName);
const outputPath = getFolderPath(downloadsFolder, platform);
const expectedVersion = getExpectedVersion(version, chromeMode);
if (await existsAsync(outputPath)) {
const installedVersion = (0, exports.readVersionFile)(chromeMode);
if (installedVersion === expectedVersion) {
return (0, exports.getRevisionInfo)(chromeMode);
}
// VERSION file missing or mismatched - delete and re-download
fs.rmSync(outputPath, { recursive: true, force: true });
}
if (!(await existsAsync(downloadsFolder))) {
await mkdirAsync(downloadsFolder, {
recursive: true,
});
}
if (os.platform() !== 'darwin' &&
os.platform() !== 'linux' &&
os.arch() === 'arm64') {
throw new Error([
'Chrome Headless Shell is not available for Windows for arm64 architecture.',
].join('\n'));
}
(0, get_chrome_download_url_1.logDownloadUrl)({ url: downloadURL, logLevel, indent });
try {
await (0, download_file_1.downloadFile)({
url: downloadURL,
to: () => archivePath,
onProgress: (progress) => {
if (progress.totalSize === null || progress.percent === null) {
throw new Error('Expected totalSize and percent to be defined');
}
onProgress({
downloadedBytes: progress.downloaded,
totalSizeInBytes: progress.totalSize,
percent: progress.percent,
alreadyAvailable: false,
});
},
indent,
logLevel,
abortSignal: new AbortController().signal,
});
await (0, extract_zip_1.default)(archivePath, { dir: outputPath });
const possibleSubdirs = [
'chrome-linux',
'chrome-headless-shell-linux64',
'chromium-headless-shell-amazon-linux2023-arm64',
'chromium-headless-shell-amazon-linux2023-x64',
];
for (const subdir of possibleSubdirs) {
const chromeLinuxFolder = path.join(outputPath, subdir);
const chromePath = path.join(chromeLinuxFolder, 'chrome');
if (fs.existsSync(chromePath)) {
const chromeHeadlessShellPath = path.join(chromeLinuxFolder, 'chrome-headless-shell');
fs.renameSync(chromePath, chromeHeadlessShellPath);
}
if (fs.existsSync(chromeLinuxFolder)) {
const targetFolder = path.join(outputPath, 'chrome-headless-shell-' + platform);
if (chromeLinuxFolder !== targetFolder) {
fs.renameSync(chromeLinuxFolder, targetFolder);
}
}
}
}
catch (err) {
return Promise.reject(err);
}
finally {
if (await existsAsync(archivePath)) {
await unlinkAsync(archivePath);
}
}
writeVersionFile(chromeMode, expectedVersion);
const revisionInfo = (0, exports.getRevisionInfo)(chromeMode);
(0, make_file_executable_1.makeFileExecutableIfItIsNot)(revisionInfo.executablePath);
return revisionInfo;
};
exports.downloadBrowser = downloadBrowser;
const getFolderPath = (downloadsFolder, platform) => {
return path.resolve(downloadsFolder, platform);
};
const getExecutablePath = (chromeMode) => {
const downloadsFolder = getDownloadsFolder(chromeMode);
const platform = getPlatform();
const folderPath = getFolderPath(downloadsFolder, platform);
if (chromeMode === 'chrome-for-testing') {
if (platform === 'mac-arm64' || platform === 'mac-x64') {
return path.join(folderPath, `chrome-${platform}`, 'Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing');
}
if (platform === 'win64') {
return path.join(folderPath, 'chrome-win64', 'chrome.exe');
}
if (platform === 'linux64' || platform === 'linux-arm64') {
return path.join(folderPath, 'chrome-linux64', 'chrome');
}
throw new Error('unsupported platform' + platform);
}
if (chromeMode === 'headless-shell') {
return path.join(folderPath, `chrome-headless-shell-${platform}`, platform === 'win64'
? 'chrome-headless-shell.exe'
: platform === 'linux-arm64' || (0, get_chrome_download_url_1.isAmazonLinux2023)()
? 'headless_shell'
: 'chrome-headless-shell');
}
throw new Error('unsupported chrome mode' + chromeMode);
};
const getRevisionInfo = (chromeMode) => {
const executablePath = getExecutablePath(chromeMode);
const downloadsFolder = getDownloadsFolder(chromeMode);
const platform = getPlatform();
const folderPath = getFolderPath(downloadsFolder, platform);
const url = (0, get_chrome_download_url_1.getChromeDownloadUrl)({ platform, version: null, chromeMode });
const local = fs.existsSync(folderPath);
return {
executablePath,
folderPath,
local,
url,
};
};
exports.getRevisionInfo = getRevisionInfo;
@@ -0,0 +1,105 @@
import { BrowserLog } from '../browser-log';
import type { LogLevel } from '../log-level';
import type { HeadlessBrowser } from './Browser';
import type { CDPSession } from './Connection';
import { ConsoleMessage } from './ConsoleMessage';
import type { EvaluateFn, EvaluateFnReturnType, EvaluateHandleFn, SerializableOrJSHandle, UnwrapPromiseLike } from './EvalTypes';
import { EventEmitter } from './EventEmitter';
import type { Frame } from './FrameManager';
import type { HTTPResponse } from './HTTPResponse';
import type { JSHandle } from './JSHandle';
import type { Viewport } from './PuppeteerViewport';
import type { Target } from './Target';
import { TaskQueue } from './TaskQueue';
import type { SourceMapGetter } from './source-map-getter';
interface WaitForOptions {
timeout?: number;
}
export declare const enum PageEmittedEvents {
Error = "error",
Disposed = "disposed"
}
interface PageEventObject {
console: ConsoleMessage;
error: Error;
disposed: undefined;
}
export type OnLog = ({ logLevel, previewString, tag }: {
logLevel: LogLevel;
tag: string;
previewString: string;
}) => void;
export declare class Page extends EventEmitter {
#private;
id: string;
static _create({ client, target, defaultViewport, browser, sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
client: CDPSession;
target: Target;
defaultViewport: Viewport;
browser: HeadlessBrowser;
sourceMapGetter: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
}): Promise<Page>;
closed: boolean;
browser: HeadlessBrowser;
screenshotTaskQueue: TaskQueue;
sourceMapGetter: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
constructor({ client, target, browser, sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
client: CDPSession;
target: Target;
browser: HeadlessBrowser;
sourceMapGetter: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
});
/**
* Listen to page events.
*/
on<K extends keyof PageEventObject>(eventName: K, handler: (event: PageEventObject[K]) => void): EventEmitter;
once<K extends keyof PageEventObject>(eventName: K, handler: (event: PageEventObject[K]) => void): EventEmitter;
off<K extends keyof PageEventObject>(eventName: K, handler: (event: PageEventObject[K]) => void): EventEmitter;
/**
* @returns A target this page was created from.
*/
target(): Target;
_client(): CDPSession;
/**
* @returns The page's main frame.
* @remarks
* Page is guaranteed to have a main frame which persists during navigations.
*/
mainFrame(): Frame;
setViewport(viewport: Viewport): Promise<void>;
setDefaultNavigationTimeout(timeout: number): void;
setDefaultTimeout(timeout: number): void;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandlerType>;
url(): string;
goto({ url, timeout, options }: {
url: string;
timeout: number;
options?: WaitForOptions & {
referer?: string;
};
}): Promise<HTTPResponse | null>;
bringToFront(): Promise<void>;
setAutoDarkModeOverride(): Promise<void>;
evaluate<T extends EvaluateFn>(pageFunction: T, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
evaluateOnNewDocument(pageFunction: Function | string, ...args: unknown[]): Promise<void>;
close(options?: {
runBeforeUnload?: boolean;
}): Promise<void>;
setBrowserSourceMapGetter(context: SourceMapGetter): void;
}
export {};
@@ -0,0 +1,454 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Page = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const no_react_1 = require("remotion/no-react");
const format_logs_1 = require("../format-logs");
const logger_1 = require("../logger");
const truthy_1 = require("../truthy");
const ConsoleMessage_1 = require("./ConsoleMessage");
const EventEmitter_1 = require("./EventEmitter");
const FrameManager_1 = require("./FrameManager");
const JSHandle_1 = require("./JSHandle");
const TaskQueue_1 = require("./TaskQueue");
const TimeoutSettings_1 = require("./TimeoutSettings");
const assert_1 = require("./assert");
const util_1 = require("./util");
const shouldHideWarning = (log) => {
// Mixed Content warnings caused by localhost should not be displayed
if (log.text.includes('Mixed Content:') &&
log.text.includes('http://localhost:')) {
return true;
}
return false;
};
const format = (eventType, args) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const previewString = args
.filter((a) => {
var _a;
return !(a.type === 'symbol' && ((_a = a.description) === null || _a === void 0 ? void 0 : _a.includes(`__remotion_`)));
})
.map((a) => (0, format_logs_1.formatRemoteObject)(a))
.filter(Boolean)
.join(' ');
let logLevelFromRemotionLog = null;
let tag = null;
for (const a of args) {
if (a.type === 'symbol' && ((_a = a.description) === null || _a === void 0 ? void 0 : _a.includes(`__remotion_level_`))) {
logLevelFromRemotionLog = (_d = (_c = (_b = a.description) === null || _b === void 0 ? void 0 : _b.split('__remotion_level_')) === null || _c === void 0 ? void 0 : _c[1]) === null || _d === void 0 ? void 0 : _d.replace(')', '');
}
if (a.type === 'symbol' && ((_e = a.description) === null || _e === void 0 ? void 0 : _e.includes(`__remotion_tag_`))) {
tag = (_h = (_g = (_f = a.description) === null || _f === void 0 ? void 0 : _f.split('__remotion_tag_')) === null || _g === void 0 ? void 0 : _g[1]) === null || _h === void 0 ? void 0 : _h.replace(')', '');
}
}
const logLevelFromEvent = eventType === 'debug'
? 'verbose'
: eventType === 'error'
? 'error'
: eventType === 'warning'
? 'warn'
: 'verbose';
return { previewString, logLevelFromRemotionLog, logLevelFromEvent, tag };
};
class Page extends EventEmitter_1.EventEmitter {
id;
static async _create({ client, target, defaultViewport, browser, sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
const page = new Page({
client,
target,
browser,
sourceMapGetter,
logLevel,
indent,
pageIndex,
onBrowserLog,
onLog,
});
await page.#initialize();
await page.setViewport(defaultViewport);
return page;
}
closed = false;
#client;
#target;
#timeoutSettings = new TimeoutSettings_1.TimeoutSettings();
#frameManager;
#pageBindings = new Map();
browser;
screenshotTaskQueue;
sourceMapGetter;
logLevel;
indent;
pageIndex;
onBrowserLog;
onLog;
constructor({ client, target, browser, sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
super();
this.#client = client;
this.#target = target;
this.#frameManager = new FrameManager_1.FrameManager(client, this, indent, logLevel);
this.screenshotTaskQueue = new TaskQueue_1.TaskQueue();
this.browser = browser;
this.id = String(Math.random());
this.sourceMapGetter = sourceMapGetter;
this.logLevel = logLevel;
this.indent = indent;
this.pageIndex = pageIndex;
this.onBrowserLog = onBrowserLog;
this.onLog = onLog;
client.on('Target.attachedToTarget', (event) => {
switch (event.targetInfo.type) {
case 'iframe':
break;
case 'worker':
break;
default:
// If we don't detach from service workers, they will never die.
// We still want to attach to workers for emitting events.
// We still want to attach to iframes so sessions may interact with them.
// We detach from all other types out of an abundance of caution.
// See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
// for the complete list of available types.
client
.send('Target.detachFromTarget', {
sessionId: event.sessionId,
})
.catch((err) => logger_1.Log.error({ indent, logLevel }, err));
}
});
client.on('Runtime.consoleAPICalled', (event) => {
return this.#onConsoleAPI(event);
});
client.on('Runtime.bindingCalled', (event) => {
return this.#onBindingCalled(event);
});
client.on('Inspector.targetCrashed', () => {
return this.#onTargetCrashed();
});
client.on('Log.entryAdded', (event) => {
return this.#onLogEntryAdded(event);
});
}
#onConsole = (log) => {
var _a, _b;
var _c;
const stackTrace = log.stackTrace();
const { url, columnNumber, lineNumber } = (_c = stackTrace[0]) !== null && _c !== void 0 ? _c : {};
const logLevel = this.logLevel;
const indent = this.indent;
if (shouldHideWarning(log)) {
return;
}
(_a = this.onBrowserLog) === null || _a === void 0 ? void 0 : _a.call(this, {
stackTrace,
text: log.text,
type: log.type,
});
if ((url === null || url === void 0 ? void 0 : url.endsWith(no_react_1.NoReactInternals.bundleName)) &&
lineNumber &&
this.sourceMapGetter()) {
const origPosition = (_b = this.sourceMapGetter()) === null || _b === void 0 ? void 0 : _b.originalPositionFor({
column: columnNumber !== null && columnNumber !== void 0 ? columnNumber : 0,
line: lineNumber,
});
const file = [
origPosition === null || origPosition === void 0 ? void 0 : origPosition.source,
origPosition === null || origPosition === void 0 ? void 0 : origPosition.line,
origPosition === null || origPosition === void 0 ? void 0 : origPosition.column,
]
.filter(truthy_1.truthy)
.join(':');
const isDelayRenderClear = log.previewString.includes(no_react_1.NoReactInternals.DELAY_RENDER_CLEAR_TOKEN);
const tabInfo = `Tab ${this.pageIndex}`;
const tagInfo = [origPosition === null || origPosition === void 0 ? void 0 : origPosition.name, isDelayRenderClear ? null : file]
.filter(truthy_1.truthy)
.join('@');
const tag = [tabInfo, log.tag, log.tag ? null : tagInfo]
.filter(truthy_1.truthy)
.join(', ');
this.onLog({
logLevel: log.logLevel,
tag,
previewString: log.previewString,
});
}
else if (log.type === 'error') {
if (log.text.includes('Failed to load resource:')) {
logger_1.Log.error({ logLevel, tag: url, indent },
// Sometimes the log is like this:
// Failed to load resource: the server responded with a status of 404 ()
// We remove the empty parentheses.
log.text.replace(/\(\)$/, ''));
}
else {
logger_1.Log.error({ logLevel, tag: `console.${log.type}`, indent }, log.text);
}
}
else {
logger_1.Log.verbose({ logLevel, tag: `console.${log.type}`, indent }, log.text);
}
};
async #initialize() {
await Promise.all([
this.#frameManager.initialize(),
this.#client.send('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: false,
flatten: true,
}),
this.#client.send('Performance.enable'),
this.#client.send('Log.enable'),
]);
}
/**
* Listen to page events.
*/
// Note: this method exists to define event typings and handle
// proper wireup of cooperative request interception. Actual event listening and
// dispatching is delegated to EventEmitter.
on(eventName, handler) {
return super.on(eventName, handler);
}
once(eventName, handler) {
// Note: this method only exists to define the types; we delegate the impl
// to EventEmitter.
return super.once(eventName, handler);
}
off(eventName, handler) {
return super.off(eventName, handler);
}
/**
* @returns A target this page was created from.
*/
target() {
return this.#target;
}
_client() {
return this.#client;
}
#onTargetCrashed() {
// This error message is being checked against in is-flaky-error.ts
this.emit('error', new Error('Page crashed!'));
}
#onLogEntryAdded(event) {
var _a;
const { level, text, args, source, url, lineNumber } = event.entry;
if (args) {
args.map((arg) => {
return (0, util_1.releaseObject)(this.#client, arg);
});
}
const { previewString, logLevelFromRemotionLog, logLevelFromEvent, tag } = format(level, args !== null && args !== void 0 ? args : []);
if (source !== 'worker') {
const message = new ConsoleMessage_1.ConsoleMessage({
type: level,
text,
args: [],
stackTraceLocations: [{ url, lineNumber }],
previewString,
logLevel: logLevelFromRemotionLog !== null && logLevelFromRemotionLog !== void 0 ? logLevelFromRemotionLog : logLevelFromEvent,
tag,
});
(_a = this.onBrowserLog) === null || _a === void 0 ? void 0 : _a.call(this, {
stackTrace: message.stackTrace(),
text: message.text,
type: message.type,
});
this.#onConsole(message);
}
}
/**
* @returns The page's main frame.
* @remarks
* Page is guaranteed to have a main frame which persists during navigations.
*/
mainFrame() {
return this.#frameManager.mainFrame();
}
async setViewport(viewport) {
const fromSurface = !process.env.DISABLE_FROM_SURFACE;
const request = fromSurface
? {
mobile: false,
width: viewport.width,
height: viewport.height,
deviceScaleFactor: viewport.deviceScaleFactor,
screenOrientation: {
angle: 0,
type: 'portraitPrimary',
},
}
: {
mobile: false,
width: viewport.width,
height: viewport.height,
deviceScaleFactor: 1,
screenHeight: viewport.height,
screenWidth: viewport.width,
scale: viewport.deviceScaleFactor,
viewport: {
height: viewport.height * viewport.deviceScaleFactor,
width: viewport.width * viewport.deviceScaleFactor,
scale: 1,
x: 0,
y: 0,
},
};
const { value } = await this.#client.send('Emulation.setDeviceMetricsOverride', request);
return value;
}
setDefaultNavigationTimeout(timeout) {
this.#timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this.#timeoutSettings.setDefaultTimeout(timeout);
}
async evaluateHandle(pageFunction, ...args) {
const context = await this.mainFrame().executionContext();
return context.evaluateHandle(pageFunction, ...args);
}
#onConsoleAPI(event) {
if (event.executionContextId === 0) {
return;
}
const context = this.#frameManager.executionContextById(event.executionContextId, this.#client);
const values = event.args.map((arg) => {
return (0, JSHandle_1._createJSHandle)(context, arg);
});
this.#addConsoleMessage(event.type, values, event.stackTrace);
}
async #onBindingCalled(event) {
let payload;
try {
payload = JSON.parse(event.payload);
}
catch (_a) {
// The binding was either called by something in the page or it was
// called before our wrapper was initialized.
return;
}
const { type, name, seq, args } = payload;
if (type !== 'exposedFun' || !this.#pageBindings.has(name)) {
return;
}
let expression = null;
try {
const pageBinding = this.#pageBindings.get(name);
(0, assert_1.assert)(pageBinding);
const result = await pageBinding(...args);
expression = (0, util_1.pageBindingDeliverResultString)(name, seq, result);
}
catch (_error) {
if ((0, util_1.isErrorLike)(_error)) {
expression = (0, util_1.pageBindingDeliverErrorString)(name, seq, _error.message, _error.stack);
}
else {
expression = (0, util_1.pageBindingDeliverErrorValueString)(name, seq, _error);
}
}
await this.#client.send('Runtime.evaluate', {
expression,
contextId: event.executionContextId,
});
}
#addConsoleMessage(eventType, args, stackTrace) {
var _a, _b;
const textTokens = [];
for (const arg of args) {
const remoteObject = arg._remoteObject;
if (remoteObject.objectId) {
textTokens.push(arg.toString());
}
else {
textTokens.push((0, util_1.valueFromRemoteObject)(remoteObject));
}
}
const stackTraceLocations = [];
if (stackTrace) {
for (const callFrame of stackTrace.callFrames) {
stackTraceLocations.push({
url: callFrame.url,
lineNumber: callFrame.lineNumber,
columnNumber: callFrame.columnNumber,
});
}
}
const { previewString, logLevelFromRemotionLog, logLevelFromEvent, tag } = format(eventType, (_a = args.map((a) => a._remoteObject)) !== null && _a !== void 0 ? _a : []);
const logLevel = (_b = logLevelFromRemotionLog) !== null && _b !== void 0 ? _b : logLevelFromEvent;
const message = new ConsoleMessage_1.ConsoleMessage({
type: eventType,
text: textTokens.join(' '),
args,
stackTraceLocations,
previewString,
logLevel,
tag,
});
this.#onConsole(message);
}
url() {
return this.mainFrame().url();
}
goto({ url, timeout, options = {}, }) {
return this.#frameManager.mainFrame().goto(url, timeout, options);
}
async bringToFront() {
await this.#client.send('Page.bringToFront');
}
async setAutoDarkModeOverride() {
const result = await this.#client.send('Emulation.setEmulatedMedia', {
media: 'screen',
features: [
{
name: 'prefers-color-scheme',
value: 'dark',
},
],
});
console.log(result);
}
evaluate(pageFunction, ...args) {
return this.#frameManager.mainFrame().evaluate(pageFunction, ...args);
}
async evaluateOnNewDocument(pageFunction, ...args) {
const source = (0, util_1.evaluationString)(pageFunction, ...args);
await this.#client.send('Page.addScriptToEvaluateOnNewDocument', {
source,
});
}
async close(options = { runBeforeUnload: undefined }) {
const connection = this.#client.connection();
if (!connection) {
return;
}
const runBeforeUnload = Boolean(options.runBeforeUnload);
if (runBeforeUnload) {
await this.#client.send('Page.close');
}
else {
await connection.send('Target.closeTarget', {
targetId: this.#target._targetId,
});
await this.#target._isClosedPromise;
}
}
setBrowserSourceMapGetter(context) {
this.sourceMapGetter = context;
}
}
exports.Page = Page;
@@ -0,0 +1,32 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Connection } from './Connection';
export declare const makeBrowserRunner: ({ executablePath, processArguments, userDataDir, logLevel, indent, timeout, }: {
executablePath: string;
processArguments: string[];
userDataDir: string;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
indent: boolean;
timeout: number;
}) => Promise<{
listeners: (() => import("./EventEmitter").CommonEventEmitter)[];
deleteBrowserCaches: () => void;
forgetEventLoop: () => void;
rememberEventLoop: () => void;
connection: Connection;
closeProcess: () => Promise<void>;
}>;
export type BrowserRunner = Awaited<ReturnType<typeof makeBrowserRunner>>;
@@ -0,0 +1,303 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeBrowserRunner = void 0;
const childProcess = __importStar(require("node:child_process"));
const node_path_1 = require("node:path");
const delete_directory_1 = require("../delete-directory");
const log_level_1 = require("../log-level");
const logger_1 = require("../logger");
const truthy_1 = require("../truthy");
const Connection_1 = require("./Connection");
const Errors_1 = require("./Errors");
const NodeWebSocketTransport_1 = require("./NodeWebSocketTransport");
const assert_1 = require("./assert");
const should_log_message_1 = require("./should-log-message");
const util_1 = require("./util");
const PROCESS_ERROR_EXPLANATION = `Puppeteer was unable to kill the process which ran the browser binary.
This means that, on future Puppeteer launches, Puppeteer might not be able to launch the browser.
Please check your open processes and ensure that the browser processes that Puppeteer launched have been killed.
If you think this is a bug, please report it on the Puppeteer issue tracker.`;
const makeBrowserRunner = async ({ executablePath, processArguments, userDataDir, logLevel, indent, timeout, }) => {
var _a, _b;
const dumpio = (0, log_level_1.isEqualOrBelowLogLevel)(logLevel, 'verbose');
const stdio = dumpio
? ['ignore', 'pipe', 'pipe']
: ['pipe', 'pipe', 'pipe'];
const proc = childProcess.spawn(executablePath, processArguments, {
// On non-windows platforms, `detached: true` makes child process a
// leader of a new process group, making it possible to kill child
// process tree with `.kill(-pid)` command. @see
// https://nodejs.org/api/child_process.html#child_process_options_detached
detached: process.platform !== 'win32',
env: process.env,
stdio,
});
const browserWSEndpoint = await waitForWSEndpoint({
browserProcess: proc,
timeout,
indent,
logLevel,
});
const transport = await NodeWebSocketTransport_1.NodeWebSocketTransport.create(browserWSEndpoint);
const connection = new Connection_1.Connection(transport);
const killProcess = () => {
// If the process failed to launch (for example if the browser executable path
// is invalid), then the process does not get a pid assigned. A call to
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
if (proc.pid && pidExists(proc.pid)) {
try {
if (process.platform === 'win32') {
childProcess.exec(`taskkill /pid ${proc.pid} /T /F`, (error) => {
if (error) {
// taskkill can fail to kill the process e.g. due to missing permissions.
// Let's kill the process via Node API. This delays killing of all child
// processes of `this.proc` until the main Node.js process dies.
proc.kill();
}
});
}
else {
// on linux the process group can be killed with the group id prefixed with
// a minus sign. The process group id is the group leader's pid.
const processGroupId = -proc.pid;
logger_1.Log.verbose({ indent, logLevel }, `Trying to kill browser process group ${processGroupId}`);
try {
process.kill(processGroupId, 'SIGKILL');
}
catch (error) {
// Killing the process group can fail due e.g. to missing permissions.
// Let's kill the process via Node API. This delays killing of all child
// processes of `this.proc` until the main Node.js process dies.
logger_1.Log.verbose({ indent, logLevel }, `Could not kill browser process group ${processGroupId}. Killing process via Node.js API`);
proc.kill('SIGKILL');
}
}
}
catch (error) {
throw new Error(`${PROCESS_ERROR_EXPLANATION}\nError cause: ${(0, util_1.isErrorLike)(error) ? error.stack : error}`);
}
}
(0, delete_directory_1.deleteDirectory)(userDataDir);
// Cleanup this listener last, as that makes sure the full callback runs. If we
// perform this earlier, then the previous function calls would not happen.
(0, util_1.removeEventListeners)(listeners);
};
const closeProcess = () => {
if (closed) {
return Promise.resolve();
}
logger_1.Log.verbose({ indent, logLevel }, 'Received SIGTERM signal. Killing browser process');
killProcess();
(0, delete_directory_1.deleteDirectory)(userDataDir);
// Cleanup this listener last, as that makes sure the full callback runs. If we
// perform this earlier, then the previous function calls would not happen.
(0, util_1.removeEventListeners)(listeners);
return processClosing;
};
if (dumpio) {
(_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (d) => {
const message = d.toString('utf8').trim();
if ((0, should_log_message_1.shouldLogBrowserMessage)(message)) {
const formatted = (0, should_log_message_1.formatChromeMessage)(message);
if (!formatted) {
return;
}
const { output, tag } = formatted;
logger_1.Log.verbose({ indent, logLevel, tag }, output);
}
});
(_b = proc.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (d) => {
const message = d.toString('utf8').trim();
if ((0, should_log_message_1.shouldLogBrowserMessage)(message)) {
const formatted = (0, should_log_message_1.formatChromeMessage)(message);
if (!formatted) {
return;
}
const { output, tag } = formatted;
logger_1.Log.error({ indent, logLevel, tag }, output);
}
});
}
let closed = false;
const processClosing = new Promise((fulfill, reject) => {
proc.once('exit', () => {
closed = true;
// Cleanup as processes exit.
try {
fulfill();
}
catch (error) {
reject(error);
}
});
});
const listeners = [(0, util_1.addEventListener)(process, 'exit', killProcess)];
listeners.push((0, util_1.addEventListener)(process, 'SIGINT', () => {
killProcess();
process.exit(130);
}));
listeners.push((0, util_1.addEventListener)(process, 'SIGTERM', closeProcess));
listeners.push((0, util_1.addEventListener)(process, 'SIGHUP', closeProcess));
const deleteBrowserCaches = () => {
// We leave some data:
// Default/Cookies
// Default/Local Storage
// Default/Session Storage
// DevToolsActivePort
// Because not sure if it is bad to delete them while Chrome is running.
const cachePaths = [
(0, node_path_1.join)(userDataDir, 'Default', 'Cache', 'Cache_Data'),
(0, node_path_1.join)(userDataDir, 'Default', 'Code Cache'),
(0, node_path_1.join)(userDataDir, 'Default', 'DawnCache'),
(0, node_path_1.join)(userDataDir, 'Default', 'GPUCache'),
];
for (const p of cachePaths) {
(0, delete_directory_1.deleteDirectory)(p);
}
};
const rememberEventLoop = () => {
var _a, _b;
proc.ref();
// @ts-expect-error
(_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.ref();
// @ts-expect-error
(_b = proc.stderr) === null || _b === void 0 ? void 0 : _b.ref();
(0, assert_1.assert)(connection, 'BrowserRunner not connected.');
connection.transport.rememberEventLoop();
};
const forgetEventLoop = () => {
var _a, _b;
proc.unref();
// @ts-expect-error
(_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.unref();
// @ts-expect-error
(_b = proc.stderr) === null || _b === void 0 ? void 0 : _b.unref();
(0, assert_1.assert)(connection, 'BrowserRunner not connected.');
connection.transport.forgetEventLoop();
};
return {
listeners,
deleteBrowserCaches,
forgetEventLoop,
rememberEventLoop,
connection,
closeProcess,
};
};
exports.makeBrowserRunner = makeBrowserRunner;
function waitForWSEndpoint({ browserProcess, timeout, logLevel, indent, }) {
const browserStderr = browserProcess.stderr;
const browserStdout = browserProcess.stdout;
(0, assert_1.assert)(browserStderr, '`browserProcess` does not have stderr.');
(0, assert_1.assert)(browserStdout, '`browserProcess` does not have stdout.');
let stdioString = '';
return new Promise((resolve, reject) => {
browserStderr.addListener('data', onStdIoData);
browserStdout.addListener('data', onStdIoData);
browserStderr.addListener('close', onClose);
const listeners = [
() => browserStderr.removeListener('data', onStdIoData),
() => browserStdout.removeListener('data', onStdIoData),
() => browserStderr.removeListener('close', onClose),
(0, util_1.addEventListener)(browserProcess, 'exit', (code, signal) => {
logger_1.Log.verbose({ indent, logLevel }, 'Browser process exited with code', code, 'signal', signal);
return onClose(new Error(`Closed with ${code} signal: ${signal}`));
}),
(0, util_1.addEventListener)(browserProcess, 'error', (error) => {
return onClose(error);
}),
];
const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0;
function onClose(error) {
cleanup();
reject(new Error([
'Failed to launch the browser process!',
error ? error.stack : null,
stdioString,
'Troubleshooting: https://remotion.dev/docs/troubleshooting/browser-launch',
]
.filter(truthy_1.truthy)
.join('\n')));
}
function onTimeout() {
cleanup();
reject(new Errors_1.TimeoutError(`Timed out after ${timeout} ms while trying to connect to the browser! Chrome logged the following: ${stdioString}`));
}
function onStdIoData(data) {
stdioString += data.toString('utf8');
const match = stdioString.match(/DevTools listening on (ws:\/\/.*)/);
if (!match) {
return;
}
cleanup();
resolve(match[1]);
}
function cleanup() {
if (timeoutId) {
clearTimeout(timeoutId);
}
(0, util_1.removeEventListeners)(listeners);
}
});
}
function pidExists(pid) {
try {
return process.kill(pid, 0);
}
catch (error) {
if ((0, util_1.isErrnoException)(error)) {
if (error.code && error.code === 'ESRCH') {
return false;
}
}
throw error;
}
}
@@ -0,0 +1,49 @@
import type { Commands } from './devtools-commands';
import type { TargetInfo } from './devtools-types';
import { EventEmitter } from './EventEmitter';
import type { NodeWebSocketTransport } from './NodeWebSocketTransport';
export declare class Connection extends EventEmitter {
#private;
transport: NodeWebSocketTransport;
constructor(transport: NodeWebSocketTransport);
static fromSession(session: CDPSession): Connection | undefined;
session(sessionId: string): CDPSession | null;
send<T extends keyof Commands>(method: T, ...paramArgs: Commands[T]['paramsType']): Promise<{
value: Commands[T]['returnType'];
size: number;
}>;
_rawSend(message: Record<string, unknown>): number;
dispose(): void;
/**
* @param targetInfo - The target info
* @returns The CDP session that is created
*/
createSession(targetInfo: TargetInfo): Promise<CDPSession>;
}
interface CDPSessionOnMessageObject {
id?: number;
method: string;
params: Record<string, unknown>;
error: {
message: string;
data: any;
code: number;
};
result?: any;
}
export declare const CDPSessionEmittedEvents: {
readonly Disconnected: symbol;
};
export declare class CDPSession extends EventEmitter {
#private;
constructor(connection: Connection, targetType: string, sessionId: string);
connection(): Connection | undefined;
send<T extends keyof Commands>(method: T, ...paramArgs: Commands[T]['paramsType']): Promise<{
value: Commands[T]['returnType'];
size: number;
}>;
_onMessage(object: CDPSessionOnMessageObject, size: number): void;
_onClosed(): void;
id(): string;
}
export {};
@@ -0,0 +1,245 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CDPSession = exports.CDPSessionEmittedEvents = exports.Connection = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const logger_1 = require("../logger");
const Errors_1 = require("./Errors");
const EventEmitter_1 = require("./EventEmitter");
const ConnectionEmittedEvents = {
Disconnected: Symbol('Connection.Disconnected'),
};
class Connection extends EventEmitter_1.EventEmitter {
transport;
#lastId = 0;
#sessions = new Map();
#closed = false;
#callbacks = new Map();
constructor(transport) {
super();
this.transport = transport;
this.transport.onmessage = this.#onMessage.bind(this);
this.transport.onclose = this.#onClose.bind(this);
}
static fromSession(session) {
return session.connection();
}
session(sessionId) {
return this.#sessions.get(sessionId) || null;
}
send(method, ...paramArgs) {
// There is only ever 1 param arg passed, but the Protocol defines it as an
// array of 0 or 1 items See this comment:
// https://github.com/ChromeDevTools/devtools-protocol/pull/113#issuecomment-412603285
// which explains why the protocol defines the params this way for better
// type-inference.
// So now we check if there are any params or not and deal with them accordingly.
const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this._rawSend({ method, params });
return new Promise((resolve, reject) => {
var _a;
this.#callbacks.set(id, {
resolve,
reject,
method,
returnSize: true,
stack: (_a = new Error().stack) !== null && _a !== void 0 ? _a : '',
fn: method + JSON.stringify(params),
});
});
}
_rawSend(message) {
const id = ++this.#lastId;
const stringifiedMessage = JSON.stringify({ ...message, id });
this.transport.send(stringifiedMessage);
return id;
}
#onMessage(message) {
const object = JSON.parse(message);
if (object.method === 'Target.attachedToTarget') {
const { sessionId } = object.params;
const session = new CDPSession(this, object.params.targetInfo.type, sessionId);
this.#sessions.set(sessionId, session);
this.emit('sessionattached', session);
const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) {
parentSession.emit('sessionattached', session);
}
}
else if (object.method === 'Target.detachedFromTarget') {
const session = this.#sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this.#sessions.delete(object.params.sessionId);
this.emit('sessiondetached', session);
const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) {
parentSession.emit('sessiondetached', session);
}
}
}
if (object.sessionId) {
const session = this.#sessions.get(object.sessionId);
if (session) {
session._onMessage(object, message.length);
}
}
else if (object.id) {
const callback = this.#callbacks.get(object.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
if (callback) {
this.#callbacks.delete(object.id);
if (object.error) {
callback.reject(createProtocolError(callback.method, object));
}
else if (callback.returnSize) {
callback.resolve({ value: object.result, size: message.length });
}
else {
callback.resolve(object.result);
}
}
}
else {
this.emit(object.method, object.params);
}
}
#onClose() {
if (this.#closed) {
return;
}
this.transport.onmessage = undefined;
this.transport.onclose = undefined;
for (const callback of this.#callbacks.values()) {
callback.reject(rewriteError(new Errors_1.ProtocolError(), `Protocol error (${callback.method}): Target closed. https://www.remotion.dev/docs/target-closed`));
}
this.#callbacks.clear();
for (const session of this.#sessions.values()) {
session._onClosed();
}
this.#sessions.clear();
this.emit(ConnectionEmittedEvents.Disconnected);
}
dispose() {
this.#onClose();
this.transport.close();
}
/**
* @param targetInfo - The target info
* @returns The CDP session that is created
*/
async createSession(targetInfo) {
const { value: { sessionId }, } = await this.send('Target.attachToTarget', {
targetId: targetInfo.targetId,
flatten: true,
});
const session = this.#sessions.get(sessionId);
if (!session) {
throw new Error('CDPSession creation failed.');
}
return session;
}
}
exports.Connection = Connection;
exports.CDPSessionEmittedEvents = {
Disconnected: Symbol('CDPSession.Disconnected'),
};
class CDPSession extends EventEmitter_1.EventEmitter {
#sessionId;
#targetType;
#callbacks = new Map();
#connection;
constructor(connection, targetType, sessionId) {
super();
this.#connection = connection;
this.#targetType = targetType;
this.#sessionId = sessionId;
}
connection() {
return this.#connection;
}
send(method, ...paramArgs) {
if (!this.#connection) {
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this.#targetType} has been closed.`));
}
// See the comment in Connection#send explaining why we do this.
const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this.#connection._rawSend({
sessionId: this.#sessionId,
method,
params,
});
return new Promise((resolve, reject) => {
var _a;
if (this.#callbacks.size > 100) {
for (const callback of this.#callbacks.values()) {
logger_1.Log.info({ indent: false, logLevel: 'info' }, callback.fn);
}
throw new Error('Leak detected: Too many callbacks');
}
this.#callbacks.set(id, {
resolve,
reject,
method,
returnSize: true,
stack: (_a = new Error().stack) !== null && _a !== void 0 ? _a : '',
fn: method + JSON.stringify(params),
});
});
}
_onMessage(object, size) {
const callback = object.id ? this.#callbacks.get(object.id) : undefined;
if (object.id && callback) {
this.#callbacks.delete(object.id);
if (object.error) {
callback.reject(createProtocolError(callback.method, object));
}
else if (callback.returnSize) {
callback.resolve({ value: object.result, size });
}
else {
callback.resolve(object.result);
}
}
else {
this.emit(object.method, object.params);
}
}
_onClosed() {
this.#connection = undefined;
for (const callback of this.#callbacks.values()) {
callback.reject(rewriteError(new Errors_1.ProtocolError(), `Protocol error (${callback.method}): Target closed. https://www.remotion.dev/docs/target-closed`));
}
this.#callbacks.clear();
this.emit(exports.CDPSessionEmittedEvents.Disconnected);
}
id() {
return this.#sessionId;
}
}
exports.CDPSession = CDPSession;
function createProtocolError(method, object) {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error) {
message += ` ${object.error.data}`;
}
return rewriteError(new Errors_1.ProtocolError(), message, object.error.message);
}
function rewriteError(error, message, originalMessage) {
error.message = message;
error.originalMessage = originalMessage !== null && originalMessage !== void 0 ? originalMessage : error.originalMessage;
return error;
}
@@ -0,0 +1,42 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LogLevel } from 'remotion';
import type { JSHandle } from './JSHandle';
export interface ConsoleMessageLocation {
url?: string;
lineNumber?: number;
columnNumber?: number;
}
export type ConsoleMessageType = 'log' | 'debug' | 'info' | 'error' | 'warning' | 'dir' | 'dirxml' | 'table' | 'trace' | 'clear' | 'startGroup' | 'startGroupCollapsed' | 'endGroup' | 'assert' | 'profile' | 'profileEnd' | 'count' | 'timeEnd' | 'verbose';
export declare class ConsoleMessage {
#private;
type: ConsoleMessageType;
text: string;
args: JSHandle[];
previewString: string;
logLevel: LogLevel;
tag: string | null;
constructor({ type, text, args, stackTraceLocations, previewString, logLevel, tag }: {
type: ConsoleMessageType;
text: string;
args: JSHandle[];
stackTraceLocations: ConsoleMessageLocation[];
previewString: string;
logLevel: LogLevel;
tag: string | null;
});
stackTrace(): ConsoleMessageLocation[];
}
@@ -0,0 +1,40 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleMessage = void 0;
class ConsoleMessage {
type;
text;
args;
previewString;
#stackTraceLocations;
logLevel;
tag;
constructor({ type, text, args, stackTraceLocations, previewString, logLevel, tag, }) {
this.type = type;
this.text = text;
this.args = args;
this.previewString = previewString;
this.#stackTraceLocations = stackTraceLocations;
this.logLevel = logLevel;
this.tag = tag;
}
stackTrace() {
return this.#stackTraceLocations;
}
}
exports.ConsoleMessage = ConsoleMessage;
@@ -0,0 +1,56 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { HeadlessBrowser } from './Browser';
import type { EvaluateFn, EvaluateFnReturnType, EvaluateHandleFn, SerializableOrJSHandle, UnwrapPromiseLike } from './EvalTypes';
import type { ExecutionContext } from './ExecutionContext';
import type { Frame } from './FrameManager';
import type { JSHandle } from './JSHandle';
export declare class DOMWorld {
#private;
get _waitTasks(): Set<WaitTask>;
constructor(frame: Frame);
frame(): Frame;
_setContext(context: ExecutionContext | null): void;
_hasContext(): boolean;
_detach(): void;
executionContext(): Promise<ExecutionContext>;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandlerType>;
evaluate<T extends EvaluateFn>(pageFunction: T, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
waitForFunction({ browser, timeout, pageFunction, title }: {
browser: HeadlessBrowser;
timeout: number | null;
pageFunction: Function | string;
title: string;
}): WaitTask;
}
interface WaitTaskOptions {
domWorld: DOMWorld;
predicateBody: Function | string;
title: string;
timeout: number | null;
browser: HeadlessBrowser;
args: SerializableOrJSHandle[];
}
declare class WaitTask {
#private;
promise: Promise<JSHandle>;
constructor(options: WaitTaskOptions);
onBrowserClose: () => void;
onBrowserCloseSilent: () => void;
terminate(error: Error | null): void;
rerun(): Promise<void>;
}
export {};
@@ -0,0 +1,258 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOMWorld = void 0;
const assert_1 = require("./assert");
const Errors_1 = require("./Errors");
const util_1 = require("./util");
class DOMWorld {
#frame;
#contextPromise = null;
#contextResolveCallback = null;
#detached = false;
#waitTasks = new Set();
get _waitTasks() {
return this.#waitTasks;
}
constructor(frame) {
// Keep own reference to client because it might differ from the FrameManager's
// client for OOP iframes.
this.#frame = frame;
this._setContext(null);
}
frame() {
return this.#frame;
}
_setContext(context) {
var _a;
if (context) {
(0, assert_1.assert)(this.#contextResolveCallback, 'Execution Context has already been set.');
(_a = this.#contextResolveCallback) === null || _a === void 0 ? void 0 : _a.call(null, context);
this.#contextResolveCallback = null;
for (const waitTask of this._waitTasks) {
waitTask.rerun();
}
}
else {
this.#contextPromise = new Promise((fulfill) => {
this.#contextResolveCallback = fulfill;
});
}
}
_hasContext() {
return !this.#contextResolveCallback;
}
_detach() {
this.#detached = true;
for (const waitTask of this._waitTasks) {
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
}
}
executionContext() {
if (this.#detached) {
throw new Error(`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`);
}
if (this.#contextPromise === null) {
throw new Error(`Execution content promise is missing`);
}
return this.#contextPromise;
}
async evaluateHandle(pageFunction, ...args) {
const context = await this.executionContext();
return context.evaluateHandle(pageFunction, ...args);
}
async evaluate(pageFunction, ...args) {
const context = await this.executionContext();
return context.evaluate(pageFunction, ...args);
}
waitForFunction({ browser, timeout, pageFunction, title, }) {
return new WaitTask({
domWorld: this,
predicateBody: pageFunction,
title,
timeout,
args: [],
browser,
});
}
}
exports.DOMWorld = DOMWorld;
const noop = () => undefined;
class WaitTask {
#domWorld;
#timeout;
#predicateBody;
#args;
#runCount = 0;
#resolve = noop;
#reject = noop;
#timeoutTimer;
#terminated = false;
#browser;
promise;
constructor(options) {
function getPredicateBody(predicateBody) {
if ((0, util_1.isString)(predicateBody)) {
return `return (${predicateBody});`;
}
return `return (${predicateBody})(...args);`;
}
this.#domWorld = options.domWorld;
this.#timeout = options.timeout;
this.#predicateBody = getPredicateBody(options.predicateBody);
this.#args = options.args;
this.#runCount = 0;
this.#domWorld._waitTasks.add(this);
this.promise = new Promise((resolve, reject) => {
this.#resolve = resolve;
this.#reject = reject;
});
// Since page navigation requires us to re-install the pageScript, we should track
// timeout on our end.
if (options.timeout) {
const timeoutError = new Errors_1.TimeoutError(`waiting for ${options.title} failed: timeout ${options.timeout}ms exceeded`);
this.#timeoutTimer = setTimeout(() => {
return this.#reject(timeoutError);
}, options.timeout);
}
this.#browser = options.browser;
this.#browser.on("closed" /* BrowserEmittedEvents.Closed */, this.onBrowserClose);
this.#browser.on("closed-silent" /* BrowserEmittedEvents.ClosedSilent */, this.onBrowserCloseSilent);
this.rerun();
}
onBrowserClose = () => {
return this.terminate(new Error('Browser was closed'));
};
onBrowserCloseSilent = () => {
return this.terminate(null);
};
terminate(error) {
this.#terminated = true;
if (error) {
this.#reject(error);
}
this.#cleanup();
}
async rerun() {
const runCount = ++this.#runCount;
let success = null;
let error = null;
const context = await this.#domWorld.executionContext();
if (this.#terminated || runCount !== this.#runCount) {
return;
}
if (this.#terminated || runCount !== this.#runCount) {
return;
}
try {
success = await context.evaluateHandle(waitForPredicatePageFunction, this.#predicateBody, this.#timeout, ...this.#args);
}
catch (error_) {
error = error_;
}
if (this.#terminated || runCount !== this.#runCount) {
if (success) {
await success.dispose();
}
return;
}
// Ignore timeouts in pageScript - we track timeouts ourselves.
// If the frame's execution context has already changed, `frame.evaluate` will
// throw an error - ignore this predicate run altogether.
if (!error &&
(await this.#domWorld
.evaluate((s) => {
return !s;
}, success)
.catch(() => {
return true;
}))) {
if (!success) {
throw new Error('Assertion: result handle is not available');
}
await success.dispose();
return;
}
if (error) {
if (error.message.includes('TypeError: binding is not a function')) {
return this.rerun();
}
// When frame is detached the task should have been terminated by the DOMWorld.
// This can fail if we were adding this task while the frame was detached,
// so we terminate here instead.
if (error.message.includes('Execution context is not available in detached frame')) {
this.terminate(new Error('waitForFunction failed: frame got detached.'));
return;
}
// When the page is navigated, the promise is rejected.
// We will try again in the new execution context.
if (error.message.includes('Execution context was destroyed')) {
return;
}
// We could have tried to evaluate in a context which was already
// destroyed.
if (error.message.includes('Cannot find context with specified id')) {
return;
}
this.#reject(error);
}
else {
if (!success) {
throw new Error('Assertion: result handle is not available');
}
this.#resolve(success);
}
this.#cleanup();
}
#cleanup() {
if (this.#timeoutTimer !== undefined) {
clearTimeout(this.#timeoutTimer);
}
this.#browser.off("closed" /* BrowserEmittedEvents.Closed */, this.onBrowserClose);
this.#browser.off("closed-silent" /* BrowserEmittedEvents.ClosedSilent */, this.onBrowserCloseSilent);
if (this.#domWorld._waitTasks.size > 100) {
throw new Error('Leak detected: Too many WaitTasks');
}
this.#domWorld._waitTasks.delete(this);
}
}
function waitForPredicatePageFunction(predicateBody, timeout, ...args) {
// eslint-disable-next-line no-new-func
const predicate = new Function('...args', predicateBody);
let timedOut = false;
if (timeout) {
setTimeout(() => {
timedOut = true;
}, timeout);
}
return new Promise((resolve) => {
async function onRaf() {
if (timedOut) {
resolve(undefined);
return;
}
const success = await predicate(...args);
if (success) {
resolve(success);
}
else {
requestAnimationFrame(onRaf);
}
}
onRaf();
});
}
@@ -0,0 +1,25 @@
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare class CustomError extends Error {
constructor(message?: string);
}
export declare class TimeoutError extends CustomError {
}
export declare class ProtocolError extends CustomError {
code?: number;
originalMessage: string;
}
export {};
@@ -0,0 +1,33 @@
"use strict";
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtocolError = exports.TimeoutError = void 0;
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class TimeoutError extends CustomError {
}
exports.TimeoutError = TimeoutError;
class ProtocolError extends CustomError {
code;
originalMessage = '';
}
exports.ProtocolError = ProtocolError;
@@ -0,0 +1,27 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { JSHandle } from './JSHandle';
export type EvaluateFn<T = any, U = any, V = any> = string | ((arg1: T, ...args: U[]) => V);
export type UnwrapPromiseLike<T> = T extends PromiseLike<infer U> ? U : T;
export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (...args: any[]) => infer R ? R : any;
export type EvaluateHandleFn = string | ((...args: any[]) => any);
type Serializable = number | string | boolean | null | bigint | JSONArray | JSONObject;
type JSONArray = readonly Serializable[];
interface JSONObject {
[key: string]: Serializable;
}
export type SerializableOrJSHandle = Serializable | JSHandle;
export {};
@@ -0,0 +1,17 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,23 @@
import type { EventType, Handler } from './mitt';
export interface CommonEventEmitter {
on(event: EventType, handler: Handler): CommonEventEmitter;
off(event: EventType, handler: Handler): CommonEventEmitter;
addListener(event: EventType, handler: Handler): CommonEventEmitter;
emit(event: EventType, eventData?: unknown): boolean;
once(event: EventType, handler: Handler): CommonEventEmitter;
listenerCount(event: string): number;
removeAllListeners(event?: EventType): CommonEventEmitter;
}
export declare class EventEmitter implements CommonEventEmitter {
private emitter;
private eventsMap;
constructor();
on(event: EventType, handler: Handler): EventEmitter;
off(event: EventType, handler: Handler): EventEmitter;
addListener(event: EventType, handler: Handler): EventEmitter;
emit(event: EventType, eventData?: unknown): boolean;
once(event: EventType, handler: Handler): EventEmitter;
listenerCount(event: EventType): number;
removeAllListeners(event?: EventType): EventEmitter;
private eventListenersCount;
}
@@ -0,0 +1,54 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventEmitter = void 0;
const mitt_1 = __importDefault(require("./mitt"));
class EventEmitter {
emitter;
eventsMap = new Map();
constructor() {
this.emitter = (0, mitt_1.default)(this.eventsMap);
}
on(event, handler) {
this.emitter.on(event, handler);
return this;
}
off(event, handler) {
this.emitter.off(event, handler);
return this;
}
addListener(event, handler) {
this.on(event, handler);
return this;
}
emit(event, eventData) {
this.emitter.emit(event, eventData);
return this.eventListenersCount(event) > 0;
}
once(event, handler) {
const onceHandler = (eventData) => {
handler(eventData);
this.off(event, onceHandler);
};
return this.on(event, onceHandler);
}
listenerCount(event) {
return this.eventListenersCount(event);
}
removeAllListeners(event) {
if (event) {
this.eventsMap.delete(event);
}
else {
this.eventsMap.clear();
}
return this;
}
eventListenersCount(event) {
var _a;
return ((_a = this.eventsMap.get(event)) === null || _a === void 0 ? void 0 : _a.length) || 0;
}
}
exports.EventEmitter = EventEmitter;
@@ -0,0 +1,34 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { CDPSession } from './Connection';
import type { ExecutionContextDescription } from './devtools-types';
import type { DOMWorld } from './DOMWorld';
import type { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
import type { Frame } from './FrameManager';
import type { ElementHandle } from './JSHandle';
import { JSHandle } from './JSHandle';
export declare const EVALUATION_SCRIPT_URL = "pptr://__puppeteer_evaluation_script__";
export declare class ExecutionContext {
#private;
_client: CDPSession;
_world: DOMWorld;
_contextId: number;
_contextName: string;
constructor(client: CDPSession, contextPayload: ExecutionContextDescription, world: DOMWorld);
frame(): Frame | null;
evaluate<ReturnType>(pageFunction: Function | string, ...args: unknown[]): Promise<ReturnType>;
evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
}
@@ -0,0 +1,171 @@
"use strict";
/* eslint-disable no-new-func */
/* eslint-disable no-new */
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecutionContext = exports.EVALUATION_SCRIPT_URL = void 0;
const JSHandle_1 = require("./JSHandle");
const util_1 = require("./util");
exports.EVALUATION_SCRIPT_URL = 'pptr://__puppeteer_evaluation_script__';
const SOURCE_URL_REGEX = /^[\x20\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
class ExecutionContext {
_client;
_world;
_contextId;
_contextName;
constructor(client, contextPayload, world) {
this._client = client;
this._world = world;
this._contextId = contextPayload.id;
this._contextName = contextPayload.name;
}
frame() {
return this._world ? this._world.frame() : null;
}
evaluate(pageFunction, ...args) {
return this.#evaluate(true, pageFunction, ...args);
}
evaluateHandle(pageFunction, ...args) {
return this.#evaluate(false, pageFunction, ...args);
}
async #evaluate(returnByValue, pageFunction, ...args) {
const suffix = `//# sourceURL=${exports.EVALUATION_SCRIPT_URL}`;
if ((0, util_1.isString)(pageFunction)) {
const contextId = this._contextId;
const expression = pageFunction;
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression)
? expression
: expression + '\n' + suffix;
const { value: { exceptionDetails: _details, result: _remoteObject }, } = await this._client
.send('Runtime.evaluate', {
expression: expressionWithSourceUrl,
contextId,
returnByValue,
awaitPromise: true,
userGesture: true,
})
.catch(rewriteError);
if (_details) {
throw new Error('Evaluation failed: ' + (0, util_1.getExceptionMessage)(_details));
}
return returnByValue
? (0, util_1.valueFromRemoteObject)(_remoteObject)
: (0, JSHandle_1._createJSHandle)(this, _remoteObject);
}
if (typeof pageFunction !== 'function') {
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
}
let functionText = pageFunction.toString();
try {
new Function('(' + functionText + ')');
}
catch (error) {
// This means we might have a function shorthand. Try another
// time prefixing 'function '.
if (functionText.startsWith('async ')) {
functionText =
'async function ' + functionText.substring('async '.length);
}
else {
functionText = 'function ' + functionText;
}
try {
new Function('(' + functionText + ')');
}
catch (_error) {
// We tried hard to serialize, but there's a weird beast here.
throw new Error('Passed function is not well-serializable!');
}
}
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue,
awaitPromise: true,
userGesture: true,
});
}
catch (error) {
if (error instanceof TypeError &&
error.message.startsWith('Converting circular structure to JSON')) {
error.message += ' Recursive objects are not allowed.';
}
throw error;
}
const { value: { exceptionDetails, result: remoteObject }, } = await callFunctionOnPromise.catch(rewriteError);
if (exceptionDetails) {
throw new Error('Evaluation failed: ' + (0, util_1.getExceptionMessage)(exceptionDetails));
}
return returnByValue
? (0, util_1.valueFromRemoteObject)(remoteObject)
: (0, JSHandle_1._createJSHandle)(this, remoteObject);
function convertArgument(arg) {
if (typeof arg === 'bigint') {
// eslint-disable-line valid-typeof
return { unserializableValue: `${arg.toString()}n` };
}
if (Object.is(arg, -0)) {
return { unserializableValue: '-0' };
}
if (Object.is(arg, Infinity)) {
return { unserializableValue: 'Infinity' };
}
if (Object.is(arg, -Infinity)) {
return { unserializableValue: '-Infinity' };
}
if (Object.is(arg, NaN)) {
return { unserializableValue: 'NaN' };
}
const objectHandle = arg && arg instanceof JSHandle_1.JSHandle ? arg : null;
if (objectHandle) {
if (objectHandle._context !== this) {
throw new Error('JSHandles can be evaluated only in the context they were created!');
}
if (objectHandle._disposed) {
throw new Error('JSHandle is disposed!');
}
if (objectHandle._remoteObject.unserializableValue) {
return {
unserializableValue: objectHandle._remoteObject.unserializableValue,
};
}
if (!objectHandle._remoteObject.objectId) {
return { value: objectHandle._remoteObject.value };
}
return { objectId: objectHandle._remoteObject.objectId };
}
return { value: arg };
}
function rewriteError(error) {
if (error.message.includes('Object reference chain is too long')) {
return { value: { result: { type: 'undefined' } }, size: 1 };
}
if (error.message.includes("Object couldn't be returned by value")) {
return { value: { result: { type: 'undefined' } }, size: 1 };
}
if (error.message.endsWith('Cannot find context with specified id') ||
error.message.endsWith('Inspected target navigated or closed')) {
throw new Error('Execution context was destroyed, most likely because of a navigation.');
}
throw error;
}
}
}
exports.ExecutionContext = ExecutionContext;
@@ -0,0 +1,89 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { LogLevel } from '../log-level';
import type { Page } from './BrowserPage';
import type { CDPSession } from './Connection';
import { DOMWorld } from './DOMWorld';
import type { EvaluateFn, EvaluateFnReturnType, EvaluateHandleFn, SerializableOrJSHandle, UnwrapPromiseLike } from './EvalTypes';
import { EventEmitter } from './EventEmitter';
import { ExecutionContext } from './ExecutionContext';
import type { HTTPResponse } from './HTTPResponse';
import type { JSHandle } from './JSHandle';
import type { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import { NetworkManager } from './NetworkManager';
import type { Frame as TFrame } from './devtools-types';
export declare const FrameManagerEmittedEvents: {
FrameNavigated: symbol;
FrameDetached: symbol;
FrameSwapped: symbol;
LifecycleEvent: symbol;
FrameNavigatedWithinDocument: symbol;
ExecutionContextCreated: symbol;
ExecutionContextDestroyed: symbol;
};
export declare class FrameManager extends EventEmitter {
#private;
get _client(): CDPSession;
constructor(client: CDPSession, page: Page, indent: boolean, logLevel: LogLevel);
private setupEventListeners;
initialize(client?: CDPSession): Promise<void>;
networkManager(): NetworkManager;
navigateFrame(frame: Frame, url: string, timeout: number, options?: {
referer?: string;
timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent;
}): Promise<HTTPResponse | null>;
page(): Page;
mainFrame(): Frame;
frames(): Frame[];
frame(frameId: string): Frame | null;
_ensureIsolatedWorld(session: CDPSession, name: string): Promise<void>;
executionContextById(contextId: number, session?: CDPSession): ExecutionContext;
}
export declare class Frame {
#private;
_frameManager: FrameManager;
_id: string;
_loaderId: string;
_name?: string;
_hasStartedLoading: boolean;
_lifecycleEvents: Set<string>;
_mainWorld: DOMWorld;
_secondaryWorld: DOMWorld;
_childFrames: Set<Frame>;
constructor(frameManager: FrameManager, parentFrame: Frame | null, frameId: string, client: CDPSession);
_updateClient(client: CDPSession): void;
isOOPFrame(): boolean;
goto(url: string, timeout: number, options?: {
referer?: string;
waitUntil?: PuppeteerLifeCycleEvent;
}): Promise<HTTPResponse | null>;
_client(): CDPSession;
/**
* @returns a promise that resolves to the frame's default execution context.
*/
executionContext(): Promise<ExecutionContext>;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandlerType>;
evaluate<T extends EvaluateFn>(pageFunction: T, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
url(): string;
childFrames(): Frame[];
_navigated(framePayload: TFrame): void;
_navigatedWithinDocument(url: string): void;
_onLifecycleEvent(loaderId: string, name: string): void;
_onLoadingStopped(): void;
_onLoadingStarted(): void;
_detach(): void;
}
@@ -0,0 +1,492 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Frame = exports.FrameManager = exports.FrameManagerEmittedEvents = void 0;
const Connection_1 = require("./Connection");
const DOMWorld_1 = require("./DOMWorld");
const EventEmitter_1 = require("./EventEmitter");
const ExecutionContext_1 = require("./ExecutionContext");
const LifecycleWatcher_1 = require("./LifecycleWatcher");
const NetworkManager_1 = require("./NetworkManager");
const assert_1 = require("./assert");
const flaky_errors_1 = require("./flaky-errors");
const util_1 = require("./util");
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
exports.FrameManagerEmittedEvents = {
FrameNavigated: Symbol('FrameManager.FrameNavigated'),
FrameDetached: Symbol('FrameManager.FrameDetached'),
FrameSwapped: Symbol('FrameManager.FrameSwapped'),
LifecycleEvent: Symbol('FrameManager.LifecycleEvent'),
FrameNavigatedWithinDocument: Symbol('FrameManager.FrameNavigatedWithinDocument'),
ExecutionContextCreated: Symbol('FrameManager.ExecutionContextCreated'),
ExecutionContextDestroyed: Symbol('FrameManager.ExecutionContextDestroyed'),
};
class FrameManager extends EventEmitter_1.EventEmitter {
#page;
#networkManager;
#frames = new Map();
#contextIdToContext = new Map();
#isolatedWorlds = new Set();
#mainFrame;
#client;
get _client() {
return this.#client;
}
constructor(client, page, indent, logLevel) {
super();
this.#client = client;
this.#page = page;
this.#networkManager = new NetworkManager_1.NetworkManager(client, this, indent, logLevel);
this.setupEventListeners(this.#client);
}
setupEventListeners(session) {
session.on('Page.frameAttached', (event) => {
this.#onFrameAttached(session, event.frameId, event.parentFrameId);
});
session.on('Page.frameNavigated', (event) => {
this.#onFrameNavigated(event.frame);
});
session.on('Page.navigatedWithinDocument', (event) => {
this.#onFrameNavigatedWithinDocument(event.frameId, event.url);
});
session.on('Page.frameDetached', (event) => {
this.#onFrameDetached(event.frameId, event.reason);
});
session.on('Page.frameStartedLoading', (event) => {
this.#onFrameStartedLoading(event.frameId);
});
session.on('Page.frameStoppedLoading', (event) => {
this.#onFrameStoppedLoading(event.frameId);
});
session.on('Runtime.executionContextCreated', (event) => {
this.#onExecutionContextCreated(event.context, session);
});
session.on('Runtime.executionContextDestroyed', (event) => {
this.#onExecutionContextDestroyed(event.executionContextId, session);
});
session.on('Runtime.executionContextsCleared', () => {
this.#onExecutionContextsCleared(session);
});
session.on('Page.lifecycleEvent', (event) => {
this.#onLifecycleEvent(event);
});
session.on('Target.attachedToTarget', (event) => {
this.#onAttachedToTarget(event);
});
session.on('Target.detachedFromTarget', (event) => {
this.#onDetachedFromTarget(event);
});
}
async initialize(client = this.#client) {
try {
const result = await Promise.all([
client.send('Page.enable'),
client.send('Page.getFrameTree'),
client === this.#client
? Promise.resolve()
: client.send('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: false,
flatten: true,
}),
]);
const { value: { frameTree }, } = result[1];
this.#handleFrameTree(client, frameTree);
await Promise.all([
client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
client.send('Runtime.enable').then(() => {
return this._ensureIsolatedWorld(client, UTILITY_WORLD_NAME);
}),
client === this.#client
? this.#networkManager.initialize()
: Promise.resolve(),
]);
}
catch (error) {
// The target might have been closed before the initialization finished.
if ((0, util_1.isErrorLike)(error) && (0, flaky_errors_1.isTargetClosedErr)(error)) {
return;
}
throw error;
}
}
networkManager() {
return this.#networkManager;
}
async navigateFrame(frame, url, timeout, options = {}) {
const { referer = undefined, waitUntil = 'load' } = options;
const watcher = new LifecycleWatcher_1.LifecycleWatcher(this, frame, waitUntil, timeout);
let error = await Promise.race([
navigate(this.#client, url, referer, frame._id),
watcher.timeoutOrTerminationPromise(),
]);
if (!error) {
error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.newDocumentNavigationPromise(),
watcher.sameDocumentNavigationPromise(),
]);
}
watcher.dispose();
if (error) {
throw error;
}
return watcher.navigationResponse();
async function navigate(client, _url, referrer, frameId) {
try {
const { value: response } = await client.send('Page.navigate', {
url: _url,
referrer,
frameId,
});
return response.errorText
? new Error(`${response.errorText} at ${_url}`)
: null;
}
catch (_error) {
if ((0, util_1.isErrorLike)(_error)) {
return _error;
}
throw _error;
}
}
}
async #onAttachedToTarget(event) {
if (event.targetInfo.type !== 'iframe') {
return;
}
const frame = this.#frames.get(event.targetInfo.targetId);
const connection = Connection_1.Connection.fromSession(this.#client);
(0, assert_1.assert)(connection);
const session = connection.session(event.sessionId);
(0, assert_1.assert)(session);
if (frame) {
frame._updateClient(session);
}
this.setupEventListeners(session);
await this.initialize(session);
}
#onDetachedFromTarget(event) {
if (!event.targetId) {
return;
}
const frame = this.#frames.get(event.targetId);
if (frame === null || frame === void 0 ? void 0 : frame.isOOPFrame()) {
// When an OOP iframe is removed from the page, it
// will only get a Target.detachedFromTarget event.
this.#removeFramesRecursively(frame);
}
}
#onLifecycleEvent(event) {
const frame = this.#frames.get(event.frameId);
if (!frame) {
return;
}
frame._onLifecycleEvent(event.loaderId, event.name);
this.emit(exports.FrameManagerEmittedEvents.LifecycleEvent, frame);
}
#onFrameStartedLoading(frameId) {
const frame = this.#frames.get(frameId);
if (!frame) {
return;
}
frame._onLoadingStarted();
}
#onFrameStoppedLoading(frameId) {
const frame = this.#frames.get(frameId);
if (!frame) {
return;
}
frame._onLoadingStopped();
this.emit(exports.FrameManagerEmittedEvents.LifecycleEvent, frame);
}
#handleFrameTree(session, frameTree) {
if (frameTree.frame.parentId) {
this.#onFrameAttached(session, frameTree.frame.id, frameTree.frame.parentId);
}
this.#onFrameNavigated(frameTree.frame);
if (!frameTree.childFrames) {
return;
}
for (const child of frameTree.childFrames) {
this.#handleFrameTree(session, child);
}
}
page() {
return this.#page;
}
mainFrame() {
(0, assert_1.assert)(this.#mainFrame, 'Requesting main frame too early!');
return this.#mainFrame;
}
frames() {
return Array.from(this.#frames.values());
}
frame(frameId) {
return this.#frames.get(frameId) || null;
}
#onFrameAttached(session, frameId, parentFrameId) {
if (this.#frames.has(frameId)) {
const _frame = this.#frames.get(frameId);
if (session && _frame.isOOPFrame()) {
// If an OOP iframes becomes a normal iframe again
// it is first attached to the parent page before
// the target is removed.
_frame._updateClient(session);
}
return;
}
(0, assert_1.assert)(parentFrameId);
const parentFrame = this.#frames.get(parentFrameId);
(0, assert_1.assert)(parentFrame);
const frame = new Frame(this, parentFrame, frameId, session);
this.#frames.set(frame._id, frame);
}
#onFrameNavigated(framePayload) {
const isMainFrame = !framePayload.parentId;
let frame = isMainFrame
? this.#mainFrame
: this.#frames.get(framePayload.id);
(0, assert_1.assert)(isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame');
// Detach all child frames first.
if (frame) {
for (const child of frame.childFrames()) {
this.#removeFramesRecursively(child);
}
}
// Update or create main frame.
if (isMainFrame) {
if (frame) {
// Update frame id to retain frame identity on cross-process navigation.
this.#frames.delete(frame._id);
frame._id = framePayload.id;
}
else {
// Initial main frame navigation.
frame = new Frame(this, null, framePayload.id, this.#client);
}
this.#frames.set(framePayload.id, frame);
this.#mainFrame = frame;
}
// Update frame payload.
(0, assert_1.assert)(frame);
frame._navigated(framePayload);
this.emit(exports.FrameManagerEmittedEvents.FrameNavigated, frame);
}
async _ensureIsolatedWorld(session, name) {
const key = `${session.id()}:${name}`;
if (this.#isolatedWorlds.has(key)) {
return;
}
this.#isolatedWorlds.add(key);
await session.send('Page.addScriptToEvaluateOnNewDocument', {
source: `//# sourceURL=${ExecutionContext_1.EVALUATION_SCRIPT_URL}`,
worldName: name,
});
// Frames might be removed before we send this.
await Promise.all(this.frames()
.filter((frame) => {
return frame._client() === session;
})
.map((frame) => {
return session
.send('Page.createIsolatedWorld', {
frameId: frame._id,
worldName: name,
grantUniveralAccess: true,
})
.catch(() => undefined);
}));
}
#onFrameNavigatedWithinDocument(frameId, url) {
const frame = this.#frames.get(frameId);
if (!frame) {
return;
}
frame._navigatedWithinDocument(url);
this.emit(exports.FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame);
this.emit(exports.FrameManagerEmittedEvents.FrameNavigated, frame);
}
#onFrameDetached(frameId, reason) {
const frame = this.#frames.get(frameId);
if (reason === 'remove') {
// Only remove the frame if the reason for the detached event is
// an actual removement of the frame.
// For frames that become OOP iframes, the reason would be 'swap'.
if (frame) {
this.#removeFramesRecursively(frame);
}
}
else if (reason === 'swap') {
this.emit(exports.FrameManagerEmittedEvents.FrameSwapped, frame);
}
}
#onExecutionContextCreated(contextPayload, session) {
const auxData = contextPayload.auxData;
const frameId = auxData === null || auxData === void 0 ? void 0 : auxData.frameId;
const frame = typeof frameId === 'string' ? this.#frames.get(frameId) : undefined;
let world;
if (frame) {
// Only care about execution contexts created for the current session.
if (frame._client() !== session) {
return;
}
if (contextPayload.auxData && Boolean(contextPayload.auxData.isDefault)) {
world = frame._mainWorld;
}
else if (contextPayload.name === UTILITY_WORLD_NAME &&
!frame._secondaryWorld._hasContext()) {
// In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds.
// We can use either.
world = frame._secondaryWorld;
}
}
const context = new ExecutionContext_1.ExecutionContext((frame === null || frame === void 0 ? void 0 : frame._client()) || this.#client, contextPayload, world);
if (world) {
world._setContext(context);
}
const key = `${session.id()}:${contextPayload.id}`;
this.#contextIdToContext.set(key, context);
}
#onExecutionContextDestroyed(executionContextId, session) {
const key = `${session.id()}:${executionContextId}`;
const context = this.#contextIdToContext.get(key);
if (!context) {
return;
}
this.#contextIdToContext.delete(key);
if (context._world) {
context._world._setContext(null);
}
}
#onExecutionContextsCleared(session) {
for (const [key, context] of this.#contextIdToContext.entries()) {
// Make sure to only clear execution contexts that belong
// to the current session.
if (context._client !== session) {
continue;
}
if (context._world) {
context._world._setContext(null);
}
this.#contextIdToContext.delete(key);
}
}
executionContextById(contextId, session = this.#client) {
const key = `${session.id()}:${contextId}`;
const context = this.#contextIdToContext.get(key);
(0, assert_1.assert)(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context;
}
#removeFramesRecursively(frame) {
for (const child of frame.childFrames()) {
this.#removeFramesRecursively(child);
}
frame._detach();
this.#frames.delete(frame._id);
this.emit(exports.FrameManagerEmittedEvents.FrameDetached, frame);
}
}
exports.FrameManager = FrameManager;
class Frame {
#parentFrame;
#url = '';
#client;
_frameManager;
_id;
_loaderId = '';
_name;
_hasStartedLoading = false;
_lifecycleEvents = new Set();
_mainWorld;
_secondaryWorld;
_childFrames;
constructor(frameManager, parentFrame, frameId, client) {
this._frameManager = frameManager;
this.#parentFrame = parentFrame !== null && parentFrame !== void 0 ? parentFrame : null;
this.#url = '';
this._id = frameId;
this._loaderId = '';
this._childFrames = new Set();
if (this.#parentFrame) {
this.#parentFrame._childFrames.add(this);
}
this._updateClient(client);
}
_updateClient(client) {
this.#client = client;
this._mainWorld = new DOMWorld_1.DOMWorld(this);
this._secondaryWorld = new DOMWorld_1.DOMWorld(this);
}
isOOPFrame() {
return this.#client !== this._frameManager._client;
}
goto(url, timeout, options = {}) {
return this._frameManager.navigateFrame(this, url, timeout, options);
}
_client() {
return this.#client;
}
/**
* @returns a promise that resolves to the frame's default execution context.
*/
executionContext() {
return this._mainWorld.executionContext();
}
evaluateHandle(pageFunction, ...args) {
return this._mainWorld.evaluateHandle(pageFunction, ...args);
}
evaluate(pageFunction, ...args) {
return this._mainWorld.evaluate(pageFunction, ...args);
}
url() {
return this.#url;
}
childFrames() {
return Array.from(this._childFrames);
}
_navigated(framePayload) {
this._name = framePayload.name;
this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`;
}
_navigatedWithinDocument(url) {
this.#url = url;
}
_onLifecycleEvent(loaderId, name) {
if (name === 'init') {
this._loaderId = loaderId;
this._lifecycleEvents.clear();
}
this._lifecycleEvents.add(name);
}
_onLoadingStopped() {
this._lifecycleEvents.add('load');
}
_onLoadingStarted() {
this._hasStartedLoading = true;
}
_detach() {
this._mainWorld._detach();
this._secondaryWorld._detach();
if (this.#parentFrame) {
this.#parentFrame._childFrames.delete(this);
}
this.#parentFrame = null;
}
}
exports.Frame = Frame;
@@ -0,0 +1,29 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { RequestWillBeSentEvent } from './devtools-types';
import type { Frame } from './FrameManager';
import type { HTTPResponse } from './HTTPResponse';
export declare class HTTPRequest {
#private;
_requestId: string;
_response: HTTPResponse | null;
_url: string | null;
_fromMemoryCache: boolean;
constructor(frame: Frame | null, event: RequestWillBeSentEvent);
response(): HTTPResponse | null;
frame(): Frame | null;
isNavigationRequest(): boolean;
}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPRequest = void 0;
class HTTPRequest {
_requestId;
_response = null;
_url = null;
_fromMemoryCache = false;
#isNavigationRequest;
#frame;
constructor(frame, event) {
this._requestId = event.requestId;
this.#isNavigationRequest =
event.requestId === event.loaderId && event.type === 'Document';
this.#frame = frame;
this._url = event.request.url;
}
response() {
return this._response;
}
frame() {
return this.#frame;
}
isNavigationRequest() {
return this.#isNavigationRequest;
}
}
exports.HTTPRequest = HTTPRequest;
@@ -0,0 +1,21 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Response, ResponseReceivedExtraInfoEvent } from './devtools-types';
export declare class HTTPResponse {
#private;
constructor(responsePayload: Response, extraInfo: ResponseReceivedExtraInfoEvent | null);
status(): number;
}
@@ -0,0 +1,28 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPResponse = void 0;
class HTTPResponse {
#status;
constructor(responsePayload, extraInfo) {
this.#status = extraInfo ? extraInfo.statusCode : responsePayload.status;
}
status() {
return this.#status;
}
}
exports.HTTPResponse = HTTPResponse;
@@ -0,0 +1,35 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { CDPSession } from './Connection';
import type { DevtoolsRemoteObject } from './devtools-types';
import type { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
import type { ExecutionContext } from './ExecutionContext';
export declare function _createJSHandle(context: ExecutionContext, remoteObject: DevtoolsRemoteObject): JSHandle;
export declare class JSHandle {
#private;
get _disposed(): boolean;
get _remoteObject(): DevtoolsRemoteObject;
get _context(): ExecutionContext;
constructor(context: ExecutionContext, client: CDPSession, remoteObject: DevtoolsRemoteObject);
executionContext(): ExecutionContext;
evaluateHandle<HandleType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
asElement(): ElementHandle | null;
dispose(): Promise<void>;
toString(): string;
}
export declare class ElementHandle<ElementType extends Element = Element> extends JSHandle {
asElement(): ElementHandle<ElementType> | null;
}
@@ -0,0 +1,77 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElementHandle = exports.JSHandle = void 0;
exports._createJSHandle = _createJSHandle;
const util_1 = require("./util");
function _createJSHandle(context, remoteObject) {
const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) {
return new ElementHandle(context, context._client, remoteObject);
}
return new JSHandle(context, context._client, remoteObject);
}
class JSHandle {
#client;
#disposed = false;
#context;
#remoteObject;
get _disposed() {
return this.#disposed;
}
get _remoteObject() {
return this.#remoteObject;
}
get _context() {
return this.#context;
}
constructor(context, client, remoteObject) {
this.#context = context;
this.#client = client;
this.#remoteObject = remoteObject;
}
executionContext() {
return this.#context;
}
evaluateHandle(pageFunction, ...args) {
return this.executionContext().evaluateHandle(pageFunction, this, ...args);
}
asElement() {
return null;
}
async dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
await (0, util_1.releaseObject)(this.#client, this.#remoteObject);
}
toString() {
if (this.#remoteObject.objectId) {
const type = this.#remoteObject.subtype || this.#remoteObject.type;
return 'JSHandle@' + type;
}
return (0, util_1.valueFromRemoteObject)(this.#remoteObject);
}
}
exports.JSHandle = JSHandle;
class ElementHandle extends JSHandle {
asElement() {
return this;
}
}
exports.ElementHandle = ElementHandle;
@@ -0,0 +1,26 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { LogLevel } from '../log-level';
import type { Viewport } from './PuppeteerViewport';
export interface LaunchOptions {
args: string[];
executablePath: string;
logLevel: LogLevel;
indent: boolean;
defaultViewport: Viewport;
userDataDir: string;
timeout: number;
}
@@ -0,0 +1,17 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HeadlessBrowser } from './Browser';
import type { LaunchOptions } from './LaunchOptions';
export declare const launchChrome: ({ args, executablePath, defaultViewport, indent, logLevel, userDataDir, timeout, }: LaunchOptions) => Promise<HeadlessBrowser>;
@@ -0,0 +1,41 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.launchChrome = void 0;
const Browser_1 = require("./Browser");
const launchChrome = async ({ args, executablePath, defaultViewport, indent, logLevel, userDataDir, timeout, }) => {
const browser = await Browser_1.HeadlessBrowser.create({
defaultViewport,
args,
executablePath,
timeout,
userDataDir,
logLevel,
indent,
});
try {
await browser.waitForTarget((t) => {
return t.type() === 'page';
}, { timeout });
}
catch (error) {
await browser.close({ silent: false });
throw error;
}
return browser;
};
exports.launchChrome = launchChrome;
@@ -0,0 +1,29 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TimeoutError } from './Errors';
import type { Frame, FrameManager } from './FrameManager';
import type { HTTPResponse } from './HTTPResponse';
export type PuppeteerLifeCycleEvent = 'load';
export declare class LifecycleWatcher {
#private;
constructor(frameManager: FrameManager, frame: Frame, waitUntil: PuppeteerLifeCycleEvent, timeout: number);
navigationResponse(): HTTPResponse | null;
sameDocumentNavigationPromise(): Promise<Error | undefined>;
newDocumentNavigationPromise(): Promise<Error | undefined>;
lifecyclePromise(): Promise<void>;
timeoutOrTerminationPromise(): Promise<Error | TimeoutError | undefined>;
dispose(): void;
}
@@ -0,0 +1,174 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifecycleWatcher = void 0;
const assert_1 = require("./assert");
const Connection_1 = require("./Connection");
const Errors_1 = require("./Errors");
const FrameManager_1 = require("./FrameManager");
const NetworkManager_1 = require("./NetworkManager");
const util_1 = require("./util");
const puppeteerToProtocolLifecycle = new Map([['load', 'load']]);
const noop = () => undefined;
class LifecycleWatcher {
#expectedLifecycle;
#frameManager;
#frame;
#timeout;
#navigationRequest = null;
#eventListeners;
#sameDocumentNavigationCompleteCallback = noop;
#sameDocumentNavigationPromise = new Promise((fulfill) => {
this.#sameDocumentNavigationCompleteCallback = fulfill;
});
#lifecycleCallback = noop;
#lifecyclePromise = new Promise((fulfill) => {
this.#lifecycleCallback = fulfill;
});
#newDocumentNavigationCompleteCallback = noop;
#newDocumentNavigationPromise = new Promise((fulfill) => {
this.#newDocumentNavigationCompleteCallback = fulfill;
});
#terminationCallback = noop;
#terminationPromise = new Promise((fulfill) => {
this.#terminationCallback = fulfill;
});
#timeoutPromise;
#maximumTimer;
#hasSameDocumentNavigation;
#newDocumentNavigation;
#swapped;
constructor(frameManager, frame, waitUntil, timeout) {
const protocolEvent = puppeteerToProtocolLifecycle.get(waitUntil);
(0, assert_1.assert)(protocolEvent, 'Unknown value for options.waitUntil: ' + waitUntil);
this.#expectedLifecycle = [waitUntil];
this.#frameManager = frameManager;
this.#frame = frame;
this.#timeout = timeout;
this.#eventListeners = [
(0, util_1.addEventListener)(frameManager._client, Connection_1.CDPSessionEmittedEvents.Disconnected, this.#terminate.bind(this, new Error('Navigation failed because browser has disconnected!'))),
(0, util_1.addEventListener)(this.#frameManager, FrameManager_1.FrameManagerEmittedEvents.LifecycleEvent, this.#checkLifecycleComplete.bind(this)),
(0, util_1.addEventListener)(this.#frameManager, FrameManager_1.FrameManagerEmittedEvents.FrameNavigatedWithinDocument, this.#navigatedWithinDocument.bind(this)),
(0, util_1.addEventListener)(this.#frameManager, FrameManager_1.FrameManagerEmittedEvents.FrameNavigated, this.#navigated.bind(this)),
(0, util_1.addEventListener)(this.#frameManager, FrameManager_1.FrameManagerEmittedEvents.FrameSwapped, this.#frameSwapped.bind(this)),
(0, util_1.addEventListener)(this.#frameManager, FrameManager_1.FrameManagerEmittedEvents.FrameDetached, this.#onFrameDetached.bind(this)),
(0, util_1.addEventListener)(this.#frameManager.networkManager(), NetworkManager_1.NetworkManagerEmittedEvents.Request, this.#onRequest.bind(this)),
];
this.#timeoutPromise = this.#createTimeoutPromise();
this.#checkLifecycleComplete();
}
#onRequest(request) {
if (request.frame() !== this.#frame || !request.isNavigationRequest()) {
return;
}
this.#navigationRequest = request;
}
#onFrameDetached(frame) {
if (this.#frame === frame) {
this.#terminationCallback.call(null, new Error('Navigating frame was detached'));
return;
}
this.#checkLifecycleComplete();
}
navigationResponse() {
if (!this.#navigationRequest) {
return null;
}
const res = this.#navigationRequest.response();
return res;
}
#terminate(error) {
this.#terminationCallback.call(null, error);
}
sameDocumentNavigationPromise() {
return this.#sameDocumentNavigationPromise;
}
newDocumentNavigationPromise() {
return this.#newDocumentNavigationPromise;
}
lifecyclePromise() {
return this.#lifecyclePromise;
}
timeoutOrTerminationPromise() {
return Promise.race([this.#timeoutPromise, this.#terminationPromise]);
}
async #createTimeoutPromise() {
if (!this.#timeout) {
return new Promise(noop);
}
const errorMessage = 'Navigation timeout of ' + this.#timeout + ' ms exceeded';
await new Promise((fulfill) => {
this.#maximumTimer = setTimeout(fulfill, this.#timeout);
});
return new Errors_1.TimeoutError(errorMessage);
}
#navigatedWithinDocument(frame) {
if (frame !== this.#frame) {
return;
}
this.#hasSameDocumentNavigation = true;
this.#checkLifecycleComplete();
}
#navigated(frame) {
if (frame !== this.#frame) {
return;
}
this.#newDocumentNavigation = true;
this.#checkLifecycleComplete();
}
#frameSwapped(frame) {
if (frame !== this.#frame) {
return;
}
this.#swapped = true;
this.#checkLifecycleComplete();
}
#checkLifecycleComplete() {
// We expect navigation to commit.
if (!checkLifecycle(this.#frame, this.#expectedLifecycle)) {
return;
}
this.#lifecycleCallback();
if (this.#hasSameDocumentNavigation) {
this.#sameDocumentNavigationCompleteCallback();
}
if (this.#swapped || this.#newDocumentNavigation) {
this.#newDocumentNavigationCompleteCallback();
}
function checkLifecycle(frame, expectedLifecycle) {
for (const event of expectedLifecycle) {
if (!frame._lifecycleEvents.has(event)) {
return false;
}
}
for (const child of frame.childFrames()) {
if (child._hasStartedLoading &&
!checkLifecycle(child, expectedLifecycle)) {
return false;
}
}
return true;
}
}
dispose() {
(0, util_1.removeEventListeners)(this.#eventListeners);
if (this.#maximumTimer !== undefined) {
clearTimeout(this.#maximumTimer);
}
}
}
exports.LifecycleWatcher = LifecycleWatcher;
@@ -0,0 +1,34 @@
import type { LoadingFailedEvent, LoadingFinishedEvent, RequestPausedEvent, RequestWillBeSentEvent, ResponseReceivedEvent, ResponseReceivedExtraInfoEvent } from './devtools-types';
import type { HTTPRequest } from './HTTPRequest';
type QueuedEventGroup = {
responseReceivedEvent: ResponseReceivedEvent;
loadingFinishedEvent?: LoadingFinishedEvent;
loadingFailedEvent?: LoadingFailedEvent;
};
export type FetchRequestId = string;
type NetworkRequestId = string;
type RedirectInfo = {
event: RequestWillBeSentEvent;
fetchRequestId?: FetchRequestId;
};
export declare class NetworkEventManager {
#private;
forget(networkRequestId: NetworkRequestId): void;
queueFailedLoadInfo(networkRequestId: NetworkRequestId, event: LoadingFailedEvent): void;
getFailedLoadInfo(networkRequestId: NetworkRequestId): LoadingFailedEvent | undefined;
getResponseExtraInfo(networkRequestId: NetworkRequestId): ResponseReceivedExtraInfoEvent[];
private queuedRedirectInfo;
queueRedirectInfo(fetchRequestId: FetchRequestId, redirectInfo: RedirectInfo): void;
takeQueuedRedirectInfo(fetchRequestId: FetchRequestId): RedirectInfo | undefined;
storeRequestWillBeSent(networkRequestId: NetworkRequestId, event: RequestWillBeSentEvent): void;
getRequestWillBeSent(networkRequestId: NetworkRequestId): RequestWillBeSentEvent | undefined;
forgetRequestWillBeSent(networkRequestId: NetworkRequestId): void;
storeRequestPaused(networkRequestId: NetworkRequestId, event: RequestPausedEvent): void;
getRequest(networkRequestId: NetworkRequestId): HTTPRequest | undefined;
storeRequest(networkRequestId: NetworkRequestId, request: HTTPRequest): void;
forgetRequest(networkRequestId: NetworkRequestId): void;
getQueuedEventGroup(networkRequestId: NetworkRequestId): QueuedEventGroup | undefined;
queueEventGroup(networkRequestId: NetworkRequestId, event: QueuedEventGroup): void;
forgetQueuedEventGroup(networkRequestId: NetworkRequestId): void;
}
export {};
@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkEventManager = void 0;
class NetworkEventManager {
#requestWillBeSentMap = new Map();
#requestPausedMap = new Map();
#httpRequestsMap = new Map();
#responseReceivedExtraInfoMap = new Map();
#queuedRedirectInfoMap = new Map();
#queuedEventGroupMap = new Map();
#failedLoadInfoMap = new Map();
forget(networkRequestId) {
this.#requestWillBeSentMap.delete(networkRequestId);
this.#requestPausedMap.delete(networkRequestId);
this.#queuedEventGroupMap.delete(networkRequestId);
this.#queuedRedirectInfoMap.delete(networkRequestId);
this.#responseReceivedExtraInfoMap.delete(networkRequestId);
this.#failedLoadInfoMap.delete(networkRequestId);
}
queueFailedLoadInfo(networkRequestId, event) {
this.#failedLoadInfoMap.set(networkRequestId, { event });
}
getFailedLoadInfo(networkRequestId) {
var _a;
return (_a = this.#failedLoadInfoMap.get(networkRequestId)) === null || _a === void 0 ? void 0 : _a.event;
}
getResponseExtraInfo(networkRequestId) {
if (!this.#responseReceivedExtraInfoMap.has(networkRequestId)) {
this.#responseReceivedExtraInfoMap.set(networkRequestId, []);
}
return this.#responseReceivedExtraInfoMap.get(networkRequestId);
}
queuedRedirectInfo(fetchRequestId) {
if (!this.#queuedRedirectInfoMap.has(fetchRequestId)) {
this.#queuedRedirectInfoMap.set(fetchRequestId, []);
}
return this.#queuedRedirectInfoMap.get(fetchRequestId);
}
queueRedirectInfo(fetchRequestId, redirectInfo) {
this.queuedRedirectInfo(fetchRequestId).push(redirectInfo);
}
takeQueuedRedirectInfo(fetchRequestId) {
return this.queuedRedirectInfo(fetchRequestId).shift();
}
storeRequestWillBeSent(networkRequestId, event) {
this.#requestWillBeSentMap.set(networkRequestId, event);
}
getRequestWillBeSent(networkRequestId) {
return this.#requestWillBeSentMap.get(networkRequestId);
}
forgetRequestWillBeSent(networkRequestId) {
this.#requestWillBeSentMap.delete(networkRequestId);
}
storeRequestPaused(networkRequestId, event) {
this.#requestPausedMap.set(networkRequestId, event);
}
getRequest(networkRequestId) {
return this.#httpRequestsMap.get(networkRequestId);
}
storeRequest(networkRequestId, request) {
this.#httpRequestsMap.set(networkRequestId, request);
}
forgetRequest(networkRequestId) {
this.#httpRequestsMap.delete(networkRequestId);
}
getQueuedEventGroup(networkRequestId) {
return this.#queuedEventGroupMap.get(networkRequestId);
}
queueEventGroup(networkRequestId, event) {
this.#queuedEventGroupMap.set(networkRequestId, event);
}
forgetQueuedEventGroup(networkRequestId) {
this.#queuedEventGroupMap.delete(networkRequestId);
}
}
exports.NetworkEventManager = NetworkEventManager;
@@ -0,0 +1,37 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { LogLevel } from '../log-level';
import type { Commands } from './devtools-commands';
import { EventEmitter } from './EventEmitter';
import type { Frame } from './FrameManager';
export declare const NetworkManagerEmittedEvents: {
readonly Request: symbol;
};
interface CDPSession extends EventEmitter {
send<T extends keyof Commands>(method: T, ...paramArgs: Commands[T]['paramsType']): Promise<{
value: Commands[T]['returnType'];
size: number;
}>;
}
interface FrameManager {
frame(frameId: string): Frame | null;
}
export declare class NetworkManager extends EventEmitter {
#private;
constructor(client: CDPSession, frameManager: FrameManager, indent: boolean, logLevel: LogLevel);
initialize(): Promise<void>;
}
export {};
@@ -0,0 +1,253 @@
"use strict";
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkManager = exports.NetworkManagerEmittedEvents = void 0;
const EventEmitter_1 = require("./EventEmitter");
const handle_failed_resource_1 = require("./handle-failed-resource");
const HTTPRequest_1 = require("./HTTPRequest");
const HTTPResponse_1 = require("./HTTPResponse");
const NetworkEventManager_1 = require("./NetworkEventManager");
exports.NetworkManagerEmittedEvents = {
Request: Symbol('NetworkManager.Request'),
};
class NetworkManager extends EventEmitter_1.EventEmitter {
#client;
#frameManager;
#networkEventManager = new NetworkEventManager_1.NetworkEventManager();
#indent;
#logLevel;
constructor(client, frameManager, indent, logLevel) {
super();
this.#client = client;
this.#frameManager = frameManager;
this.#indent = indent;
this.#logLevel = logLevel;
this.#client.on('Fetch.requestPaused', this.#onRequestPaused.bind(this));
this.#client.on('Network.requestWillBeSent', this.#onRequestWillBeSent.bind(this));
this.#client.on('Network.requestServedFromCache', this.#onRequestServedFromCache.bind(this));
this.#client.on('Network.responseReceived', this.#onResponseReceived.bind(this));
this.#client.on('Network.loadingFinished', this.#onLoadingFinished.bind(this));
this.#client.on('Network.loadingFailed', this.#onLoadingFailed.bind(this));
this.#client.on('Network.responseReceivedExtraInfo', this.#onResponseReceivedExtraInfo.bind(this));
}
async initialize() {
await this.#client.send('Network.enable');
}
#onRequestWillBeSent(event) {
this.#onRequest(event, undefined);
}
/**
* CDP may send a Fetch.requestPaused without or before a
* Network.requestWillBeSent
*
* CDP may send multiple Fetch.requestPaused
* for the same Network.requestWillBeSent.
*/
#onRequestPaused(event) {
const { networkId: networkRequestId, requestId: fetchRequestId } = event;
if (!networkRequestId) {
return;
}
const requestWillBeSentEvent = (() => {
const _requestWillBeSentEvent = this.#networkEventManager.getRequestWillBeSent(networkRequestId);
// redirect requests have the same `requestId`,
if (_requestWillBeSentEvent &&
(_requestWillBeSentEvent.request.url !== event.request.url ||
_requestWillBeSentEvent.request.method !== event.request.method)) {
this.#networkEventManager.forgetRequestWillBeSent(networkRequestId);
return;
}
return _requestWillBeSentEvent;
})();
if (requestWillBeSentEvent) {
this.#patchRequestEventHeaders(requestWillBeSentEvent, event);
this.#onRequest(requestWillBeSentEvent, fetchRequestId);
}
else {
this.#networkEventManager.storeRequestPaused(networkRequestId, event);
}
}
#patchRequestEventHeaders(requestWillBeSentEvent, requestPausedEvent) {
requestWillBeSentEvent.request.headers = {
...requestWillBeSentEvent.request.headers,
// includes extra headers, like: Accept, Origin
...requestPausedEvent.request.headers,
};
}
#onRequest(event, fetchRequestId) {
if (event.redirectResponse) {
// We want to emit a response and requestfinished for the
// redirectResponse, but we can't do so unless we have a
// responseExtraInfo ready to pair it up with. If we don't have any
// responseExtraInfos saved in our queue, they we have to wait until
// the next one to emit response and requestfinished, *and* we should
// also wait to emit this Request too because it should come after the
// response/requestfinished.
let redirectResponseExtraInfo = null;
if (event.redirectHasExtraInfo) {
redirectResponseExtraInfo = this.#networkEventManager
.getResponseExtraInfo(event.requestId)
.shift();
if (!redirectResponseExtraInfo) {
this.#networkEventManager.queueRedirectInfo(event.requestId, {
event,
fetchRequestId,
});
return;
}
}
const _request = this.#networkEventManager.getRequest(event.requestId);
// If we connect late to the target, we could have missed the
// requestWillBeSent event.
if (_request) {
this.#handleRequestRedirect(_request, event.redirectResponse, redirectResponseExtraInfo);
}
}
const frame = event.frameId
? this.#frameManager.frame(event.frameId)
: null;
const request = new HTTPRequest_1.HTTPRequest(frame, event);
this.#networkEventManager.storeRequest(event.requestId, request);
this.emit(exports.NetworkManagerEmittedEvents.Request, request);
}
#onRequestServedFromCache(event) {
const request = this.#networkEventManager.getRequest(event.requestId);
if (request) {
request._fromMemoryCache = true;
}
}
#handleRequestRedirect(request, responsePayload, extraInfo) {
const response = new HTTPResponse_1.HTTPResponse(responsePayload, extraInfo);
request._response = response;
this.#forgetRequest(request, false);
}
#emitResponseEvent(responseReceived, extraInfo) {
const request = this.#networkEventManager.getRequest(responseReceived.requestId);
// FileUpload sends a response without a matching request.
if (!request) {
return;
}
const response = new HTTPResponse_1.HTTPResponse(responseReceived.response, extraInfo);
request._response = response;
}
#onResponseReceived(event) {
const request = this.#networkEventManager.getRequest(event.requestId);
let extraInfo = null;
if (request && !request._fromMemoryCache && event.hasExtraInfo) {
extraInfo = this.#networkEventManager
.getResponseExtraInfo(event.requestId)
.shift();
if (!extraInfo) {
// Wait until we get the corresponding ExtraInfo event.
this.#networkEventManager.queueEventGroup(event.requestId, {
responseReceivedEvent: event,
});
return;
}
}
this.#emitResponseEvent(event, extraInfo);
}
#onResponseReceivedExtraInfo(event) {
// We may have skipped a redirect response/request pair due to waiting for
// this ExtraInfo event. If so, continue that work now that we have the
// request.
const redirectInfo = this.#networkEventManager.takeQueuedRedirectInfo(event.requestId);
if (redirectInfo) {
this.#networkEventManager
.getResponseExtraInfo(event.requestId)
.push(event);
this.#onRequest(redirectInfo.event, redirectInfo.fetchRequestId);
return;
}
// We may have skipped response and loading events because we didn't have
// this ExtraInfo event yet. If so, emit those events now.
const queuedEvents = this.#networkEventManager.getQueuedEventGroup(event.requestId);
if (queuedEvents) {
this.#networkEventManager.forgetQueuedEventGroup(event.requestId);
this.#emitResponseEvent(queuedEvents.responseReceivedEvent, event);
if (queuedEvents.loadingFinishedEvent) {
this.#emitLoadingFinished(queuedEvents.loadingFinishedEvent);
}
if (queuedEvents.loadingFailedEvent) {
this.#emitLoadingFailed(queuedEvents.loadingFailedEvent);
}
return;
}
// Wait until we get another event that can use this ExtraInfo event.
this.#networkEventManager.getResponseExtraInfo(event.requestId).push(event);
}
#forgetRequest(request, events) {
const requestId = request._requestId;
this.#networkEventManager.forgetRequest(requestId);
if (events) {
this.#networkEventManager.forget(requestId);
}
}
#onLoadingFinished(event) {
// If the response event for this request is still waiting on a
// corresponding ExtraInfo event, then wait to emit this event too.
const queuedEvents = this.#networkEventManager.getQueuedEventGroup(event.requestId);
if (queuedEvents) {
queuedEvents.loadingFinishedEvent = event;
}
else {
this.#emitLoadingFinished(event);
}
}
#emitLoadingFinished(event) {
const request = this.#networkEventManager.getRequest(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request) {
return;
}
this.#forgetRequest(request, true);
}
#onLoadingFailed(event) {
// If the response event for this request is still waiting on a
// corresponding ExtraInfo event, then wait to emit this event too.
const queuedEvents = this.#networkEventManager.getQueuedEventGroup(event.requestId);
if (queuedEvents) {
queuedEvents.loadingFailedEvent = event;
}
else {
this.#emitLoadingFailed(event);
}
}
#emitLoadingFailed(event) {
const request = this.#networkEventManager.getRequest(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request) {
return;
}
if (event.canceled) {
this.#forgetRequest(request, true);
return;
}
const extraInfo = this.#networkEventManager.getResponseExtraInfo(event.requestId);
(0, handle_failed_resource_1.handleFailedResource)({
extraInfo,
event,
indent: this.#indent,
logLevel: this.#logLevel,
request,
});
this.#forgetRequest(request, true);
}
}
exports.NetworkManager = NetworkManager;
@@ -0,0 +1,19 @@
import type { WS } from '../ws/ws-types';
interface ConnectionTransport {
send(message: string): void;
close(): void;
onmessage?: (message: string) => void;
onclose?: () => void;
}
export declare class NodeWebSocketTransport implements ConnectionTransport {
static create(urlString: string): Promise<NodeWebSocketTransport>;
websocket: WS;
onmessage?: (message: string) => void;
onclose?: () => void;
constructor(ws: WS);
send(message: string): void;
close(): void;
forgetEventLoop(): void;
rememberEventLoop(): void;
}
export {};
@@ -0,0 +1,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeWebSocketTransport = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const node_dns_1 = require("node:dns");
const node_url_1 = require("node:url");
const ws_types_1 = require("../ws/ws-types");
class NodeWebSocketTransport {
static async create(urlString) {
// Starting in Node 17, IPv6 is favoured over IPv4 due to a change
// in a default option:
// - https://github.com/nodejs/node/issues/40537,
// Due to this, for Firefox, we must parse and resolve the `localhost` hostname
// manually with the previous behavior according to:
// - https://nodejs.org/api/dns.html#dnslookuphostname-options-callback
// because of https://bugzilla.mozilla.org/show_bug.cgi?id=1769994.
const url = new node_url_1.URL(urlString);
if (url.hostname === 'localhost') {
const { address } = await node_dns_1.promises.lookup(url.hostname, { verbatim: false });
url.hostname = address;
}
return new Promise((resolve, reject) => {
const ws = new ws_types_1.ws(url, [], {
followRedirects: true,
perMessageDeflate: false,
maxPayload: 1024 * 1024 * 1024, // 1024Mb
headers: {
'User-Agent': `Remotion CLI`,
},
});
ws.addEventListener('open', () => {
return resolve(new NodeWebSocketTransport(ws));
});
ws.addEventListener('error', reject);
});
}
websocket;
onmessage;
onclose;
constructor(ws) {
this.websocket = ws;
this.websocket.addEventListener('message', (event) => {
if (this.onmessage) {
this.onmessage.call(null, event.data);
}
});
this.websocket.addEventListener('close', () => {
if (this.onclose) {
this.onclose.call(null);
}
});
// Silently ignore all errors - we don't know what to do with them.
this.websocket.addEventListener('error', () => undefined);
}
send(message) {
this.websocket.send(message);
}
close() {
this.websocket.close();
}
forgetEventLoop() {
// @ts-expect-error
this.websocket._socket.unref();
}
rememberEventLoop() {
// @ts-expect-error
this.websocket._socket.ref();
}
}
exports.NodeWebSocketTransport = NodeWebSocketTransport;
@@ -0,0 +1,5 @@
export interface Viewport {
width: number;
height: number;
deviceScaleFactor: number;
}
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
"use strict";
@@ -0,0 +1,72 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BrowserLog } from '../browser-log';
import type { LogLevel } from '../log-level';
import type { BrowserContext, HeadlessBrowser } from './Browser';
import { OnLog, Page } from './BrowserPage';
import type { CDPSession } from './Connection';
import type { TargetInfo } from './devtools-types';
import type { Viewport } from './PuppeteerViewport';
import type { SourceMapGetter } from './source-map-getter';
export declare class Target {
#private;
_initializedPromise: Promise<boolean> | null;
_initializedCallback: (x: boolean) => void;
_isClosedPromise: Promise<void>;
_closedCallback: () => void;
_isInitialized: boolean;
_targetId: string;
constructor(targetInfo: TargetInfo, browserContext: BrowserContext, sessionFactory: () => Promise<CDPSession>, defaultViewport: Viewport);
/**
* Creates a Chrome Devtools Protocol session attached to the target.
*/
createCDPSession(): Promise<CDPSession>;
_getTargetInfo(): TargetInfo;
/**
* If the target is not of type `"page"` or `"background_page"`, returns `null`.
*/
page({ sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog }: {
sourceMapGetter: SourceMapGetter;
logLevel: LogLevel;
indent: boolean;
pageIndex: number;
onBrowserLog: null | ((log: BrowserLog) => void);
onLog: OnLog;
}): Promise<Page | null>;
expectPage(): Promise<Page | null>;
url(): string;
/**
* Identifies what kind of target this is.
*
* @remarks
*
* See {@link https://developer.chrome.com/extensions/background_pages | docs} for more info about background pages.
*/
type(): 'page' | 'background_page' | 'service_worker' | 'shared_worker' | 'other' | 'browser' | 'webview';
/**
* Get the browser the target belongs to.
*/
browser(): HeadlessBrowser;
/**
* Get the browser context the target belongs to.
*/
browserContext(): BrowserContext;
/**
* Get the target that opened this target. Top-level targets return `null`.
*/
opener(): Target | undefined;
_targetInfoChanged(targetInfo: TargetInfo): void;
}
@@ -0,0 +1,154 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Target = void 0;
const BrowserPage_1 = require("./BrowserPage");
const isPagetTarget = (target) => {
return (target.type === 'page' ||
target.type === 'background_page' ||
target.type === 'webview');
};
class Target {
#browserContext;
#targetInfo;
#sessionFactory;
#defaultViewport;
#pagePromise;
_initializedPromise;
_initializedCallback;
_isClosedPromise;
_closedCallback;
_isInitialized;
_targetId;
constructor(targetInfo, browserContext, sessionFactory, defaultViewport) {
this.#targetInfo = targetInfo;
this.#browserContext = browserContext;
this._targetId = targetInfo.targetId;
this.#sessionFactory = sessionFactory;
this.#defaultViewport = defaultViewport;
this._initializedPromise = new Promise((fulfill) => {
this._initializedCallback = fulfill;
}).then((success) => {
if (!success) {
return false;
}
const opener = this.opener();
if (!opener || !opener.#pagePromise || this.type() !== 'page') {
return true;
}
return true;
});
this._isClosedPromise = new Promise((fulfill) => {
this._closedCallback = fulfill;
});
this._isInitialized =
!isPagetTarget(this.#targetInfo) || this.#targetInfo.url !== '';
if (this._isInitialized) {
this._initializedCallback(true);
}
}
/**
* Creates a Chrome Devtools Protocol session attached to the target.
*/
createCDPSession() {
return this.#sessionFactory();
}
_getTargetInfo() {
return this.#targetInfo;
}
/**
* If the target is not of type `"page"` or `"background_page"`, returns `null`.
*/
async page({ sourceMapGetter, logLevel, indent, pageIndex, onBrowserLog, onLog, }) {
var _a;
if (isPagetTarget(this.#targetInfo) && !this.#pagePromise) {
this.#pagePromise = this.#sessionFactory().then((client) => {
var _a;
return BrowserPage_1.Page._create({
client,
target: this,
defaultViewport: (_a = this.#defaultViewport) !== null && _a !== void 0 ? _a : null,
browser: this.browser(),
sourceMapGetter,
logLevel,
indent,
pageIndex,
onBrowserLog,
onLog,
});
});
}
return (_a = (await this.#pagePromise)) !== null && _a !== void 0 ? _a : null;
}
async expectPage() {
var _a;
return (_a = (await this.#pagePromise)) !== null && _a !== void 0 ? _a : null;
}
url() {
return this.#targetInfo.url;
}
/**
* Identifies what kind of target this is.
*
* @remarks
*
* See {@link https://developer.chrome.com/extensions/background_pages | docs} for more info about background pages.
*/
type() {
const { type } = this.#targetInfo;
if (type === 'page' ||
type === 'background_page' ||
type === 'service_worker' ||
type === 'shared_worker' ||
type === 'browser' ||
type === 'webview') {
return type;
}
return 'other';
}
/**
* Get the browser the target belongs to.
*/
browser() {
return this.#browserContext.browser();
}
/**
* Get the browser context the target belongs to.
*/
browserContext() {
return this.#browserContext;
}
/**
* Get the target that opened this target. Top-level targets return `null`.
*/
opener() {
const { openerId } = this.#targetInfo;
if (!openerId) {
return;
}
return this.browser()._targets.get(openerId);
}
_targetInfoChanged(targetInfo) {
this.#targetInfo = targetInfo;
if (!this._isInitialized &&
(!isPagetTarget(this.#targetInfo) || this.#targetInfo.url !== '')) {
this._isInitialized = true;
this._initializedCallback(true);
}
}
}
exports.Target = Target;
@@ -0,0 +1,20 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export declare class TaskQueue {
#private;
constructor();
postTask<T>(task: () => Promise<T>): Promise<T>;
}
@@ -0,0 +1,34 @@
"use strict";
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskQueue = void 0;
class TaskQueue {
#chain;
constructor() {
this.#chain = Promise.resolve();
}
postTask(task) {
const result = this.#chain.then(task);
this.#chain = result.then(() => {
return undefined;
}, () => {
return undefined;
});
return result;
}
}
exports.TaskQueue = TaskQueue;
@@ -0,0 +1,24 @@
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export declare const DEFAULT_TIMEOUT = 30000;
export declare class TimeoutSettings {
#private;
constructor();
setDefaultTimeout(timeout: number): void;
setDefaultNavigationTimeout(timeout: number): void;
navigationTimeout(): number;
timeout(): number;
}
@@ -0,0 +1,49 @@
"use strict";
/**
* Copyright 2019 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeoutSettings = exports.DEFAULT_TIMEOUT = void 0;
exports.DEFAULT_TIMEOUT = 30000;
class TimeoutSettings {
#defaultTimeout;
#defaultNavigationTimeout;
constructor() {
this.#defaultTimeout = null;
this.#defaultNavigationTimeout = null;
}
setDefaultTimeout(timeout) {
this.#defaultTimeout = timeout;
}
setDefaultNavigationTimeout(timeout) {
this.#defaultNavigationTimeout = timeout;
}
navigationTimeout() {
if (this.#defaultNavigationTimeout !== null) {
return this.#defaultNavigationTimeout;
}
if (this.#defaultTimeout !== null) {
return this.#defaultTimeout;
}
return exports.DEFAULT_TIMEOUT;
}
timeout() {
if (this.#defaultTimeout !== null) {
return this.#defaultTimeout;
}
return exports.DEFAULT_TIMEOUT;
}
}
exports.TimeoutSettings = TimeoutSettings;
@@ -0,0 +1 @@
export declare const assert: (value: unknown, message?: string) => asserts value;
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assert = void 0;
const assert = (value, message) => {
if (!value) {
throw new Error(message);
}
};
exports.assert = assert;
@@ -0,0 +1,6 @@
import type { OnBrowserDownload } from '../options/on-browser-download';
export declare const defaultBrowserDownloadProgress: ({ indent, logLevel, api, }: {
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
api: string;
}) => OnBrowserDownload;
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultBrowserDownloadProgress = void 0;
const logger_1 = require("../logger");
const to_megabytes_1 = require("../to-megabytes");
const defaultBrowserDownloadProgress = ({ indent, logLevel, api, }) => ({ chromeMode }) => {
if (chromeMode === 'chrome-for-testing') {
logger_1.Log.info({ indent, logLevel }, 'Downloading Chrome for Testing https://www.remotion.dev/chrome-for-testing');
}
else {
logger_1.Log.info({ indent, logLevel }, 'Downloading Chrome Headless Shell https://www.remotion.dev/chrome-headless-shell');
}
logger_1.Log.info({ indent, logLevel }, `Customize this behavior by adding a onBrowserDownload function to ${api}.`);
let lastProgress = 0;
return {
onProgress: (progress) => {
if (progress.downloadedBytes > lastProgress + 10000000 ||
progress.percent === 1) {
lastProgress = progress.downloadedBytes;
if (chromeMode === 'chrome-for-testing') {
logger_1.Log.info({ indent, logLevel }, `Downloading Chrome for Testing - ${(0, to_megabytes_1.toMegabytes)(progress.downloadedBytes)}/${(0, to_megabytes_1.toMegabytes)(progress.totalSizeInBytes)}`);
}
else {
logger_1.Log.info({ indent, logLevel }, `Downloading Chrome Headless Shell - ${(0, to_megabytes_1.toMegabytes)(progress.downloadedBytes)}/${(0, to_megabytes_1.toMegabytes)(progress.totalSizeInBytes)}`);
}
}
},
version: null,
};
};
exports.defaultBrowserDownloadProgress = defaultBrowserDownloadProgress;
@@ -0,0 +1,288 @@
import type { ActivateTargetRequest, AddScriptToEvaluateOnNewDocumentRequest, AddScriptToEvaluateOnNewDocumentResponse, CallFunctionOnRequest, CallFunctionOnResponse, CaptureScreenshotRequest, CaptureScreenshotResponse, CloseTargetRequest, CloseTargetResponse, CreateIsolatedWorldRequest, CreateIsolatedWorldResponse, DetachFromTargetRequest, DevtoolsRemoteObject, EnableRequest, ExceptionDetails, GetFrameTreeResponse, NavigateRequest, NavigateResponse, PrintPDFRequest, PrintPDFResponse, ReleaseObjectRequest, SetAutoAttachRequest, SetDefaultBackgroundColorOverrideRequest, SetDeviceMetricsOverrideRequest, SetLifecycleEventsEnabledRequest } from './devtools-types';
export interface Commands {
/**
* Does nothing.
*/
'Console.clearMessages': {
paramsType: [];
returnType: void;
};
'Target.setDiscoverTargets': {
paramsType: [
{
/**
* Whether to discover available targets.
*/
discover: boolean;
}
];
returnType: void;
};
'Target.createTarget': {
paramsType: [
{
/**
* The initial URL the page will be navigated to. An empty string indicates about:blank.
*/
url: string;
/**
* Frame width in DIP (headless chrome only).
*/
width?: number;
/**
* Frame height in DIP (headless chrome only).
*/
height?: number;
/**
* The browser context to create the page in.
*/
browserContextId?: string;
/**
* Whether BeginFrames for this target will be controlled via DevTools (headless chrome only,
* not supported on MacOS yet, false by default).
*/
enableBeginFrameControl?: boolean;
/**
* Whether to create a new Window or Tab (chrome-only, false by default).
*/
newWindow?: boolean;
/**
* Whether to create the target in background or foreground (chrome-only,
* false by default).
*/
background?: boolean;
}
];
returnType: {
targetId: string;
};
};
'Browser.close': {
paramsType: [];
returnType: void;
};
'Target.attachToTarget': {
paramsType: [
{
targetId: string;
/**
* Enables "flat" access to the session via specifying sessionId attribute in the commands.
* We plan to make this the default, deprecate non-flattened mode,
* and eventually retire it. See crbug.com/991325.
*/
flatten?: boolean;
}
];
returnType: {
/**
* Id assigned to the session.
*/
sessionId: string;
};
};
'Runtime.callFunctionOn': {
paramsType: [CallFunctionOnRequest];
returnType: CallFunctionOnResponse;
};
'Runtime.evaluate': {
paramsType: [
{
/**
* Expression to evaluate.
*/
expression: string;
/**
* Symbolic group name that can be used to release multiple objects.
*/
objectGroup?: string;
/**
* Determines whether Command Line API should be available during the evaluation.
*/
includeCommandLineAPI?: boolean;
/**
* In silent mode exceptions thrown during evaluation are not reported and do not pause
* execution. Overrides `setPauseOnException` state.
*/
silent?: boolean;
/**
* Specifies in which execution context to perform evaluation. If the parameter is omitted the
* evaluation will be performed in the context of the inspected page.
* This is mutually exclusive with `uniqueContextId`, which offers an
* alternative way to identify the execution context that is more reliable
* in a multi-process environment.
*/
contextId?: number;
/**
* Whether the result is expected to be a JSON object that should be sent by value.
*/
returnByValue?: boolean;
/**
* Whether preview should be generated for the result.
*/
generatePreview?: boolean;
/**
* Whether execution should be treated as initiated by user in the UI.
*/
userGesture?: boolean;
/**
* Whether execution should `await` for resulting value and return once awaited promise is
* resolved.
*/
awaitPromise?: boolean;
/**
* Whether to throw an exception if side effect cannot be ruled out during evaluation.
* This implies `disableBreaks` below.
*/
throwOnSideEffect?: boolean;
/**
* Terminate execution after timing out (number of milliseconds).
*/
/**
* Disable breakpoints during execution.
*/
disableBreaks?: boolean;
/**
* Setting this flag to true enables `let` re-declaration and top-level `await`.
* Note that `let` variables can only be re-declared if they originate from
* `replMode` themselves.
*/
replMode?: boolean;
/**
* The Content Security Policy (CSP) for the target might block 'unsafe-eval'
* which includes eval(), Function(), setTimeout() and setInterval()
* when called with non-callable arguments. This flag bypasses CSP for this
* evaluation and allows unsafe-eval. Defaults to true.
*/
allowUnsafeEvalBlockedByCSP?: boolean;
/**
* An alternative way to specify the execution context to evaluate in.
* Compared to contextId that may be reused across processes, this is guaranteed to be
* system-unique, so it can be used to prevent accidental evaluation of the expression
* in context different than intended (e.g. as a result of navigation across process
* boundaries).
* This is mutually exclusive with `contextId`.
*/
uniqueContextId?: string;
/**
* Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.
*/
generateWebDriverValue?: boolean;
}
];
returnType: {
/**
* Evaluation result.
*/
result: DevtoolsRemoteObject;
/**
* Exception details.
*/
exceptionDetails?: ExceptionDetails;
};
};
'Page.enable': {
paramsType: [];
returnType: void;
};
'Page.getFrameTree': {
paramsType: [];
returnType: GetFrameTreeResponse;
};
'Target.setAutoAttach': {
paramsType: [SetAutoAttachRequest];
returnType: void;
};
'Page.setLifecycleEventsEnabled': {
paramsType: [SetLifecycleEventsEnabledRequest];
returnType: void;
};
'Runtime.enable': {
paramsType: [];
returnType: void;
};
'Page.navigate': {
paramsType: [NavigateRequest];
returnType: NavigateResponse;
};
'Page.addScriptToEvaluateOnNewDocument': {
paramsType: [AddScriptToEvaluateOnNewDocumentRequest];
returnType: AddScriptToEvaluateOnNewDocumentResponse;
};
'Page.createIsolatedWorld': {
paramsType: [CreateIsolatedWorldRequest];
returnType: CreateIsolatedWorldResponse;
};
'Page.captureScreenshot': {
paramsType: [CaptureScreenshotRequest?];
returnType: CaptureScreenshotResponse;
};
'Page.printToPDF': {
paramsType: [PrintPDFRequest];
returnType: PrintPDFResponse;
};
'Target.activateTarget': {
paramsType: [ActivateTargetRequest];
returnType: void;
};
'Emulation.setDefaultBackgroundColorOverride': {
paramsType: [SetDefaultBackgroundColorOverrideRequest?];
returnType: void;
};
'Network.enable': {
paramsType: [EnableRequest?];
returnType: void;
};
'Target.detachFromTarget': {
paramsType: [DetachFromTargetRequest?];
returnType: void;
};
/**
* Enable collecting and reporting metrics.
*/
'Performance.enable': {
paramsType: [EnableRequest?];
returnType: void;
};
'Log.enable': {
paramsType: [];
returnType: void;
};
'Page.setDeviceMetricsOverride': {
paramsType: [SetDeviceMetricsOverrideRequest];
returnType: void;
};
'Page.bringToFront': {
paramsType: [];
returnType: void;
};
'Emulation.setEmulatedMedia': {
paramsType: [
{
media: 'screen';
features: [
{
name: 'prefers-color-scheme';
value: 'dark';
}
];
}
];
returnType: void;
};
'Emulation.setDeviceMetricsOverride': {
paramsType: [SetDeviceMetricsOverrideRequest];
returnType: void;
};
'Target.closeTarget': {
paramsType: [CloseTargetRequest];
returnType: CloseTargetResponse;
};
'Page.close': {
paramsType: [];
returnType: void;
};
'Runtime.releaseObject': {
paramsType: [ReleaseObjectRequest];
returnType: void;
};
}
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

Some files were not shown because too many files have changed in this diff Show More