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.
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.
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.
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