@grasplabs/grasp
Version:
TypeScript SDK for browser automation and secure command execution in highly available and scalable cloud browser environments
274 lines • 11.5 kB
JavaScript
;
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.run = run;
const commander_1 = require("commander");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const dotenv_1 = require("dotenv");
const readline = __importStar(require("node:readline"));
const esbuild_1 = require("esbuild");
const ts = __importStar(require("typescript"));
const open_1 = __importDefault(require("open"));
const __1 = __importDefault(require(".."));
const promises_1 = require("node:timers/promises");
// Load environment variables
(0, dotenv_1.config)({
path: '.env.grasp',
});
const VERSION = '0.4.10';
// esbuild plugin to replace chromium.launch and chromium.launchPersistentContext with chromium.connectOverCDP
function createChromiumReplacePlugin(wsUrl, headlessRef) {
return {
name: 'chromium-replace',
setup(build) {
build.onLoad({ filter: /\.(js|ts|mjs)$/ }, async (args) => {
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
const contents = fs.readFileSync(args.path, 'utf8');
// Use TypeScript AST to transform the code
const isTypeScript = args.path.endsWith('.ts');
const sourceFile = ts.createSourceFile(args.path, contents, ts.ScriptTarget.Latest, true, isTypeScript ? ts.ScriptKind.TS : ts.ScriptKind.JS);
const transformer = context => {
return sourceFile => {
const visitor = (node) => {
// Check for method calls like browserType.launch() or browserType.launchPersistentContext()
if (ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression)) {
const methodName = node.expression.name.text;
if (methodName === 'launch' ||
methodName === 'launchPersistentContext') {
// Extract headless parameter if present
if (node.arguments.length > 0) {
const firstArg = node.arguments[0];
if (ts.isObjectLiteralExpression(firstArg)) {
for (const property of firstArg.properties) {
if (ts.isPropertyAssignment(property) &&
ts.isIdentifier(property.name) &&
property.name.text === 'headless') {
// Extract headless value
if (property.initializer.kind ===
ts.SyntaxKind.TrueKeyword) {
headlessRef.value = true;
}
else if (property.initializer.kind ===
ts.SyntaxKind.FalseKeyword) {
headlessRef.value = false;
}
break;
}
}
}
}
// Replace with connectOverCDP call
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(node.expression.expression, // Keep the browserType object
ts.factory.createIdentifier('connectOverCDP')), undefined, [ts.factory.createStringLiteral(wsUrl)]);
}
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(sourceFile, visitor);
};
};
const result = ts.transform(sourceFile, [transformer]);
const printer = ts.createPrinter();
const transformedCode = printer.printFile(result.transformed[0]);
result.dispose();
return {
contents: transformedCode,
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
};
});
},
};
}
async function buildBundleCode(file, session) {
try {
// Resolve the absolute path of the file
const filePath = (0, node_path_1.resolve)(file);
// Check if file exists
if (!(0, node_fs_1.existsSync)(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
const wsUrl = session.browser.getEndpoint();
const headlessRef = { value: true }; // Default to true
// Use esbuild to bundle the file with chromium replacement plugin
const result = await (0, esbuild_1.build)({
entryPoints: [filePath],
bundle: true,
write: false, // Don't write to disk, return the result
platform: 'node',
format: 'cjs',
target: 'node18',
minify: false,
sourcemap: false,
external: [
'playwright',
'playwright-core',
'playwright-extra',
'puppeteer-extra-plugin-stealth',
],
plugins: [createChromiumReplacePlugin(wsUrl, headlessRef)],
});
// Get the bundled code from the result
if (result.outputFiles && result.outputFiles.length > 0) {
return { code: result.outputFiles[0].text, headless: headlessRef.value };
}
else {
throw new Error('No output generated from esbuild');
}
}
catch (error) {
console.error('Error bundling code:', error);
throw error;
}
}
async function runProgram(session, file) {
// Run the specified file
const { code, headless } = await buildBundleCode(file, session);
if (!headless) {
const url = await session.browser.getLiveviewPageUrl();
if (url) {
(0, open_1.default)(url);
}
}
const result = await session.codeRunner.run(code);
await session.close();
return result;
}
// Helper function to prompt user for input
function promptForKey() {
return new Promise(resolve => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Please enter your Grasp API key: ', answer => {
rl.close();
resolve(answer.trim());
});
});
}
async function testAndOpenUrl(url) {
while (true) {
try {
const res = await fetch(url);
if (res.status < 500) {
(0, open_1.default)(url);
break;
}
}
finally {
await (0, promises_1.setTimeout)(300);
}
}
}
const program = new commander_1.Command();
program
.name('grasp-run')
.description('Run a command in a Grasp sandbox')
.version(VERSION)
.argument('<file>', 'JavaScript or TypeScript file to run with Grasp')
.option('-k, --key <string>', 'Grasp API key (optional if GRASP_KEY env var is set)')
.option('-l, --listen <number>', 'Port number to listen on', parseInt)
.helpOption('-h, --help', 'Display help for command')
.action(async (file, options) => {
let { key } = options;
// If no key provided via command line, check environment variable
if (!key) {
key = process.env.GRASP_KEY;
if (key) {
// console.log('Using Grasp API key from environment variable.');
}
else {
// console.log('No Grasp API key found in environment variable.');
key = await promptForKey();
if (!key) {
console.error('Error: Grasp API key is required to continue.');
process.exit(1);
}
}
}
console.log(`File to run: ${file}`);
try {
const grasp = new __1.default({
apiKey: key,
});
const session = await grasp.launch({
browser: {
liveview: true,
},
keepAliveMS: 10000,
timeout: 3600000,
logLevel: 'error',
});
const { listen } = options;
// Log listen parameter if provided
if (listen !== undefined) {
const host = session.getHost(listen);
// console.log(`Listen: ${host}`);
testAndOpenUrl(`https://${host}`);
}
await runProgram(session, file);
console.log('Program executed successfully.');
// Check and create .env.grasp file
const envFilePath = (0, node_path_1.join)(process.cwd(), '.env.grasp');
if (!(0, node_fs_1.existsSync)(envFilePath)) {
try {
(0, node_fs_1.writeFileSync)(envFilePath, `GRASP_KEY=${key}\n`);
console.log('Created .env.grasp file with your API key.');
}
catch (error) {
console.error('Failed to create .env.grasp file:', error);
}
}
else {
console.log('.env.grasp file already exists, will not overwrite the file content.');
}
}
catch (error) {
console.error('Error running the program:', error);
}
});
function run() {
program.parse();
}
if (require.main === module) {
run();
}
//# sourceMappingURL=grasp-run.js.map