-
Notifications
You must be signed in to change notification settings - Fork 53
Feat/eval js #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/eval js #70
Conversation
WalkthroughTwo new methods enable JavaScript execution via API: Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant APIServer
participant BrowserAgentServer
participant CDP as Chrome DevTools<br/>Protocol
Client->>APIServer: POST /page/execute<br/>{clientId, tabId, expression}
APIServer->>APIServer: Validate fields
APIServer->>APIServer: Log execution attempt
APIServer->>BrowserAgentServer: evaluateJavaScript(tabId, expression, options)
BrowserAgentServer->>CDP: Runtime.evaluate(expression)
CDP-->>BrowserAgentServer: result / exceptionDetails
BrowserAgentServer->>BrowserAgentServer: Extract result value<br/>Log outcome
BrowserAgentServer-->>APIServer: {tabId, result, exceptionDetails}
APIServer-->>Client: {clientId, tabId, result,<br/>exceptionDetails, timestamp}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
agent-server/nodejs/src/api-server.js(2 hunks)agent-server/nodejs/src/lib/BrowserAgentServer.js(1 hunks)
🧰 Additional context used
🪛 ESLint
agent-server/nodejs/src/lib/BrowserAgentServer.js
[error] 1225-1225: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1226-1226: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1227-1227: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1228-1228: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1229-1229: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: claude-review
🔇 Additional comments (1)
agent-server/nodejs/src/api-server.js (1)
150-156: /page/execute route is consistent with existing endpointsThe new
/page/executecase follows the same structure as the other POST-only routes (/tabs/open,/tabs/close,/page/content,/page/screenshot) and delegates cleanly toexecuteJavaScript(JSON.parse(body)). No issues from a routing/flow perspective.
| async executeJavaScript(payload) { | ||
| const { clientId, tabId, expression, returnByValue = true, awaitPromise = false } = payload; | ||
|
|
||
| if (!clientId) { | ||
| throw new Error('Client ID is required'); | ||
| } | ||
|
|
||
| if (!tabId) { | ||
| throw new Error('Tab ID is required'); | ||
| } | ||
|
|
||
| if (!expression) { | ||
| throw new Error('JavaScript expression is required'); | ||
| } | ||
|
|
||
| const baseClientId = clientId.split(':')[0]; | ||
|
|
||
| logger.info('Executing JavaScript', { baseClientId, tabId, expression: expression.substring(0, 100) }); | ||
|
|
||
| const result = await this.browserAgentServer.evaluateJavaScript(tabId, expression, { returnByValue, awaitPromise }); | ||
|
|
||
| return { | ||
| clientId: baseClientId, | ||
| tabId: result.tabId, | ||
| result: result.result, | ||
| exceptionDetails: result.exceptionDetails, | ||
| timestamp: Date.now() | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate expression as a non-empty string to avoid runtime errors
executeJavaScript assumes expression is a string (expression.substring(0, 100)) but currently only checks truthiness. A caller passing a non-string (e.g., object/number) will hit a runtime error and a 500 response without a clear validation message.
You can tighten validation and keep logging safe with:
async executeJavaScript(payload) {
const { clientId, tabId, expression, returnByValue = true, awaitPromise = false } = payload;
if (!clientId) {
throw new Error('Client ID is required');
}
if (!tabId) {
throw new Error('Tab ID is required');
}
- if (!expression) {
- throw new Error('JavaScript expression is required');
- }
+ if (typeof expression !== 'string' || expression.trim() === '') {
+ throw new Error('JavaScript expression must be a non-empty string');
+ }
const baseClientId = clientId.split(':')[0];
logger.info('Executing JavaScript', { baseClientId, tabId, expression: expression.substring(0, 100) });
const result = await this.browserAgentServer.evaluateJavaScript(tabId, expression, { returnByValue, awaitPromise });
return {
clientId: baseClientId,
tabId: result.tabId,
result: result.result,
exceptionDetails: result.exceptionDetails,
timestamp: Date.now()
};
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async executeJavaScript(payload) { | |
| const { clientId, tabId, expression, returnByValue = true, awaitPromise = false } = payload; | |
| if (!clientId) { | |
| throw new Error('Client ID is required'); | |
| } | |
| if (!tabId) { | |
| throw new Error('Tab ID is required'); | |
| } | |
| if (!expression) { | |
| throw new Error('JavaScript expression is required'); | |
| } | |
| const baseClientId = clientId.split(':')[0]; | |
| logger.info('Executing JavaScript', { baseClientId, tabId, expression: expression.substring(0, 100) }); | |
| const result = await this.browserAgentServer.evaluateJavaScript(tabId, expression, { returnByValue, awaitPromise }); | |
| return { | |
| clientId: baseClientId, | |
| tabId: result.tabId, | |
| result: result.result, | |
| exceptionDetails: result.exceptionDetails, | |
| timestamp: Date.now() | |
| }; | |
| } | |
| async executeJavaScript(payload) { | |
| const { clientId, tabId, expression, returnByValue = true, awaitPromise = false } = payload; | |
| if (!clientId) { | |
| throw new Error('Client ID is required'); | |
| } | |
| if (!tabId) { | |
| throw new Error('Tab ID is required'); | |
| } | |
| if (typeof expression !== 'string' || expression.trim() === '') { | |
| throw new Error('JavaScript expression must be a non-empty string'); | |
| } | |
| const baseClientId = clientId.split(':')[0]; | |
| logger.info('Executing JavaScript', { baseClientId, tabId, expression: expression.substring(0, 100) }); | |
| const result = await this.browserAgentServer.evaluateJavaScript(tabId, expression, { returnByValue, awaitPromise }); | |
| return { | |
| clientId: baseClientId, | |
| tabId: result.tabId, | |
| result: result.result, | |
| exceptionDetails: result.exceptionDetails, | |
| timestamp: Date.now() | |
| }; | |
| } |
🤖 Prompt for AI Agents
In agent-server/nodejs/src/api-server.js around lines 347 to 375, the code only
checks expression for truthiness but then calls expression.substring, which will
throw if expression is not a string; update validation to require typeof
expression === 'string' and that expression.trim().length > 0, and throw a clear
Error if not; when logging the expression preview, guard against non-strings by
using a safe conversion (e.g., String(expression).substring(0,100) or
JSON.stringify with try/catch) so logging never triggers a runtime error;
finally ensure the value passed to this.browserAgentServer.evaluateJavaScript is
the validated string.
| /** | ||
| * Execute JavaScript in a browser tab | ||
| * @param {string} tabId - Tab ID (target ID) | ||
| * @param {string} expression - JavaScript expression to execute | ||
| * @param {Object} options - Execution options | ||
| * @param {boolean} options.returnByValue - Whether to return by value (default: true) | ||
| * @param {boolean} options.awaitPromise - Whether to await promises (default: false) | ||
| * @returns {Promise<Object>} Result with execution result | ||
| */ | ||
| async evaluateJavaScript(tabId, expression, options = {}) { | ||
| const { returnByValue = true, awaitPromise = false } = options; | ||
|
|
||
| try { | ||
| logger.info('Executing JavaScript via CDP', { tabId, expressionLength: expression.length }); | ||
|
|
||
| // Use Runtime.evaluate to execute JavaScript | ||
| const result = await this.sendCDPCommandToTarget(tabId, 'Runtime.evaluate', { | ||
| expression, | ||
| returnByValue, | ||
| awaitPromise | ||
| }); | ||
|
|
||
| if (result.exceptionDetails) { | ||
| logger.warn('JavaScript execution threw exception', { | ||
| tabId, | ||
| exception: result.exceptionDetails | ||
| }); | ||
| } else { | ||
| logger.info('JavaScript executed successfully', { | ||
| tabId, | ||
| resultType: result.result?.type | ||
| }); | ||
| } | ||
|
|
||
| // Extract value from CDP RemoteObject | ||
| // CDP returns RemoteObject with structure: {type: 'string', value: 'foo'} | ||
| // For undefined/null, CDP returns: {type: 'undefined'} or {type: 'null', value: null} | ||
| // We need to check if 'value' property exists, not if it's undefined | ||
| let extractedResult; | ||
| if (result.result) { | ||
| if ('value' in result.result) { | ||
| // RemoteObject has a value field - extract it | ||
| extractedResult = result.result.value; | ||
| } else if (result.result.type === 'undefined') { | ||
| // Special case: undefined has no value field | ||
| extractedResult = undefined; | ||
| } else { | ||
| // For objects/functions without returnByValue, return the whole RemoteObject | ||
| extractedResult = result.result; | ||
| } | ||
| } else { | ||
| extractedResult = result.result; | ||
| } | ||
|
|
||
| return { | ||
| tabId, | ||
| result: extractedResult, | ||
| exceptionDetails: result.exceptionDetails | ||
| }; | ||
| } catch (error) { | ||
| logger.error('Failed to execute JavaScript via CDP', { | ||
| tabId, | ||
| error: error.message | ||
| }); | ||
| throw error; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix JSDoc hyphen lint error and harden expression handling
Two small issues here:
-
JSDoc / ESLint error
Thejsdoc/require-hyphen-before-param-descriptionrule is complaining because of the-before the descriptions (Lines [1225]-[1229]). The rest of the file seems to follow the “no hyphen” style.You can fix it with:
/** * Execute JavaScript in a browser tab - * @param {string} tabId - Tab ID (target ID) - * @param {string} expression - JavaScript expression to execute - * @param {Object} options - Execution options - * @param {boolean} options.returnByValue - Whether to return by value (default: true) - * @param {boolean} options.awaitPromise - Whether to await promises (default: false)
-
- @param {string} tabId Tab ID (target ID)
-
- @param {string} expression JavaScript expression to execute
-
- @param {Object} options Execution options
-
- @param {boolean} options.returnByValue Whether to return by value (default: true)
-
- @param {boolean} options.awaitPromise Whether to await promises (default: false)
- @returns {Promise} Result with execution result
*/2. **Guard against non-string expressions** This public method assumes `expression` is a string (`expression.length` and CDP `Runtime.evaluate`). If someone calls it programmatically with a non-string, you’ll get a runtime error before the CDP call. A lightweight guard keeps failures predictable and aligns with the JSDoc contract: ```diff async evaluateJavaScript(tabId, expression, options = {}) { - const { returnByValue = true, awaitPromise = false } = options; + const { returnByValue = true, awaitPromise = false } = options; + + if (typeof expression !== 'string') { + throw new Error('expression must be a string'); + }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.Suggested change/** * Execute JavaScript in a browser tab * @param {string} tabId - Tab ID (target ID) * @param {string} expression - JavaScript expression to execute * @param {Object} options - Execution options * @param {boolean} options.returnByValue - Whether to return by value (default: true) * @param {boolean} options.awaitPromise - Whether to await promises (default: false) * @returns {Promise<Object>} Result with execution result */ async evaluateJavaScript(tabId, expression, options = {}) { const { returnByValue = true, awaitPromise = false } = options; try { logger.info('Executing JavaScript via CDP', { tabId, expressionLength: expression.length }); // Use Runtime.evaluate to execute JavaScript const result = await this.sendCDPCommandToTarget(tabId, 'Runtime.evaluate', { expression, returnByValue, awaitPromise }); if (result.exceptionDetails) { logger.warn('JavaScript execution threw exception', { tabId, exception: result.exceptionDetails }); } else { logger.info('JavaScript executed successfully', { tabId, resultType: result.result?.type }); } // Extract value from CDP RemoteObject // CDP returns RemoteObject with structure: {type: 'string', value: 'foo'} // For undefined/null, CDP returns: {type: 'undefined'} or {type: 'null', value: null} // We need to check if 'value' property exists, not if it's undefined let extractedResult; if (result.result) { if ('value' in result.result) { // RemoteObject has a value field - extract it extractedResult = result.result.value; } else if (result.result.type === 'undefined') { // Special case: undefined has no value field extractedResult = undefined; } else { // For objects/functions without returnByValue, return the whole RemoteObject extractedResult = result.result; } } else { extractedResult = result.result; } return { tabId, result: extractedResult, exceptionDetails: result.exceptionDetails }; } catch (error) { logger.error('Failed to execute JavaScript via CDP', { tabId, error: error.message }); throw error; } } /** * Execute JavaScript in a browser tab * @param {string} tabId Tab ID (target ID) * @param {string} expression JavaScript expression to execute * @param {Object} options Execution options * @param {boolean} options.returnByValue Whether to return by value (default: true) * @param {boolean} options.awaitPromise Whether to await promises (default: false) * @returns {Promise<Object>} Result with execution result */ async evaluateJavaScript(tabId, expression, options = {}) { const { returnByValue = true, awaitPromise = false } = options; if (typeof expression !== 'string') { throw new Error('expression must be a string'); } try { logger.info('Executing JavaScript via CDP', { tabId, expressionLength: expression.length }); // Use Runtime.evaluate to execute JavaScript const result = await this.sendCDPCommandToTarget(tabId, 'Runtime.evaluate', { expression, returnByValue, awaitPromise }); if (result.exceptionDetails) { logger.warn('JavaScript execution threw exception', { tabId, exception: result.exceptionDetails }); } else { logger.info('JavaScript executed successfully', { tabId, resultType: result.result?.type }); } // Extract value from CDP RemoteObject // CDP returns RemoteObject with structure: {type: 'string', value: 'foo'} // For undefined/null, CDP returns: {type: 'undefined'} or {type: 'null', value: null} // We need to check if 'value' property exists, not if it's undefined let extractedResult; if (result.result) { if ('value' in result.result) { // RemoteObject has a value field - extract it extractedResult = result.result.value; } else if (result.result.type === 'undefined') { // Special case: undefined has no value field extractedResult = undefined; } else { // For objects/functions without returnByValue, return the whole RemoteObject extractedResult = result.result; } } else { extractedResult = result.result; } return { tabId, result: extractedResult, exceptionDetails: result.exceptionDetails }; } catch (error) { logger.error('Failed to execute JavaScript via CDP', { tabId, error: error.message }); throw error; } } 🧰 Tools
🪛 ESLint
[error] 1225-1225: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1226-1226: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1227-1227: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1228-1228: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
[error] 1229-1229: There must be no hyphen before @param description.
(jsdoc/require-hyphen-before-param-description)
🤖 Prompt for AI Agents
In agent-server/nodejs/src/lib/BrowserAgentServer.js around lines 1223-1289, fix the JSDoc hyphen lint error by removing the leading hyphens from the @param descriptions to match the file style, and harden expression handling by validating the input before using .length or passing it to CDP: if typeof expression !== 'string' throw a TypeError stating "expression must be a string", and update the logger usage to compute expressionLength safely (e.g. only access expression.length when it's a string) so non-string inputs fail with a clear, predictable error rather than a runtime crash.
|
Claude finished @olesho's task —— View job Pull Request Review: JavaScript Evaluation EndpointI've completed a comprehensive review of the JavaScript evaluation endpoint implementation. Here's my detailed analysis: SummaryThis PR adds a 📋 Code Quality & Best Practices✅ Strengths
|
Summary by CodeRabbit
Release Notes
/page/executeAPI endpoint to execute JavaScript code within browser tabs