.
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Shared utilities for MCP tools that communicate with the browser.
|
||||
* This module provides a common infrastructure for request-response
|
||||
* communication between MCP endpoints and browser sessions via HMR.
|
||||
*/
|
||||
import type { HMR_MESSAGE_SENT_TO_BROWSER, HmrMessageSentToBrowser } from '../../../dev/hot-reloader-types';
|
||||
export declare const DEFAULT_BROWSER_REQUEST_TIMEOUT_MS = 5000;
|
||||
export type BrowserResponse<T> = {
|
||||
url: string;
|
||||
data: T;
|
||||
};
|
||||
export declare function createBrowserRequest<T>(messageType: HMR_MESSAGE_SENT_TO_BROWSER, sendHmrMessage: (message: HmrMessageSentToBrowser) => void, getActiveConnectionCount: () => number, timeoutMs: number): Promise<BrowserResponse<T>[]>;
|
||||
export declare function handleBrowserPageResponse<T>(requestId: string, data: T, url: string): void;
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Shared utilities for MCP tools that communicate with the browser.
|
||||
* This module provides a common infrastructure for request-response
|
||||
* communication between MCP endpoints and browser sessions via HMR.
|
||||
*/ "use strict";
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
0 && (module.exports = {
|
||||
DEFAULT_BROWSER_REQUEST_TIMEOUT_MS: null,
|
||||
createBrowserRequest: null,
|
||||
handleBrowserPageResponse: null
|
||||
});
|
||||
function _export(target, all) {
|
||||
for(var name in all)Object.defineProperty(target, name, {
|
||||
enumerable: true,
|
||||
get: all[name]
|
||||
});
|
||||
}
|
||||
_export(exports, {
|
||||
DEFAULT_BROWSER_REQUEST_TIMEOUT_MS: function() {
|
||||
return DEFAULT_BROWSER_REQUEST_TIMEOUT_MS;
|
||||
},
|
||||
createBrowserRequest: function() {
|
||||
return createBrowserRequest;
|
||||
},
|
||||
handleBrowserPageResponse: function() {
|
||||
return handleBrowserPageResponse;
|
||||
}
|
||||
});
|
||||
const _nanoid = require("next/dist/compiled/nanoid");
|
||||
const DEFAULT_BROWSER_REQUEST_TIMEOUT_MS = 5000;
|
||||
const pendingRequests = new Map();
|
||||
function createBrowserRequest(messageType, sendHmrMessage, getActiveConnectionCount, timeoutMs) {
|
||||
const connectionCount = getActiveConnectionCount();
|
||||
if (connectionCount === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
const requestId = `mcp-${messageType}-${(0, _nanoid.nanoid)()}`;
|
||||
const responsePromise = new Promise((resolve, reject)=>{
|
||||
const timeout = setTimeout(()=>{
|
||||
const pending = pendingRequests.get(requestId);
|
||||
if (pending && pending.responses.length > 0) {
|
||||
resolve(pending.responses);
|
||||
} else {
|
||||
reject(Object.defineProperty(new Error(`Timeout waiting for response from frontend. The browser may not be responding to HMR messages.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E825",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}));
|
||||
}
|
||||
pendingRequests.delete(requestId);
|
||||
}, timeoutMs);
|
||||
pendingRequests.set(requestId, {
|
||||
responses: [],
|
||||
expectedCount: connectionCount,
|
||||
resolve: resolve,
|
||||
reject,
|
||||
timeout
|
||||
});
|
||||
});
|
||||
sendHmrMessage({
|
||||
type: messageType,
|
||||
requestId
|
||||
});
|
||||
return responsePromise;
|
||||
}
|
||||
function handleBrowserPageResponse(requestId, data, url) {
|
||||
if (!url) {
|
||||
throw Object.defineProperty(new Error('URL is required in MCP browser response. This is a bug in Next.js.'), "__NEXT_ERROR_CODE", {
|
||||
value: "E824",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
const pending = pendingRequests.get(requestId);
|
||||
if (pending) {
|
||||
pending.responses.push({
|
||||
url,
|
||||
data
|
||||
});
|
||||
if (pending.responses.length >= pending.expectedCount) {
|
||||
clearTimeout(pending.timeout);
|
||||
pending.resolve(pending.responses);
|
||||
pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=browser-communication.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../src/server/mcp/tools/utils/browser-communication.ts"],"sourcesContent":["/**\n * Shared utilities for MCP tools that communicate with the browser.\n * This module provides a common infrastructure for request-response\n * communication between MCP endpoints and browser sessions via HMR.\n */\n\nimport { nanoid } from 'next/dist/compiled/nanoid'\nimport type {\n HMR_MESSAGE_SENT_TO_BROWSER,\n HmrMessageSentToBrowser,\n} from '../../../dev/hot-reloader-types'\n\nexport const DEFAULT_BROWSER_REQUEST_TIMEOUT_MS = 5000\n\nexport type BrowserResponse<T> = {\n url: string\n data: T\n}\n\ntype PendingRequest<T> = {\n responses: BrowserResponse<T>[]\n expectedCount: number\n resolve: (value: BrowserResponse<T>[]) => void\n reject: (reason?: unknown) => void\n timeout: NodeJS.Timeout\n}\n\nconst pendingRequests = new Map<string, PendingRequest<unknown>>()\n\nexport function createBrowserRequest<T>(\n messageType: HMR_MESSAGE_SENT_TO_BROWSER,\n sendHmrMessage: (message: HmrMessageSentToBrowser) => void,\n getActiveConnectionCount: () => number,\n timeoutMs: number\n): Promise<BrowserResponse<T>[]> {\n const connectionCount = getActiveConnectionCount()\n if (connectionCount === 0) {\n return Promise.resolve([])\n }\n\n const requestId = `mcp-${messageType}-${nanoid()}`\n\n const responsePromise = new Promise<BrowserResponse<T>[]>(\n (resolve, reject) => {\n const timeout = setTimeout(() => {\n const pending = pendingRequests.get(requestId)\n if (pending && pending.responses.length > 0) {\n resolve(pending.responses as BrowserResponse<T>[])\n } else {\n reject(\n new Error(\n `Timeout waiting for response from frontend. The browser may not be responding to HMR messages.`\n )\n )\n }\n pendingRequests.delete(requestId)\n }, timeoutMs)\n\n pendingRequests.set(requestId, {\n responses: [],\n expectedCount: connectionCount,\n resolve: resolve as (value: BrowserResponse<unknown>[]) => void,\n reject,\n timeout,\n })\n }\n )\n\n sendHmrMessage({\n type: messageType,\n requestId,\n } as HmrMessageSentToBrowser)\n\n return responsePromise\n}\n\nexport function handleBrowserPageResponse<T>(\n requestId: string,\n data: T,\n url: string\n): void {\n if (!url) {\n throw new Error(\n 'URL is required in MCP browser response. This is a bug in Next.js.'\n )\n }\n\n const pending = pendingRequests.get(requestId)\n if (pending) {\n pending.responses.push({ url, data })\n if (pending.responses.length >= pending.expectedCount) {\n clearTimeout(pending.timeout)\n pending.resolve(pending.responses)\n pendingRequests.delete(requestId)\n }\n }\n}\n"],"names":["DEFAULT_BROWSER_REQUEST_TIMEOUT_MS","createBrowserRequest","handleBrowserPageResponse","pendingRequests","Map","messageType","sendHmrMessage","getActiveConnectionCount","timeoutMs","connectionCount","Promise","resolve","requestId","nanoid","responsePromise","reject","timeout","setTimeout","pending","get","responses","length","Error","delete","set","expectedCount","type","data","url","push","clearTimeout"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;;;;;;IAQYA,kCAAkC;eAAlCA;;IAiBGC,oBAAoB;eAApBA;;IA+CAC,yBAAyB;eAAzBA;;;wBAtEO;AAMhB,MAAMF,qCAAqC;AAelD,MAAMG,kBAAkB,IAAIC;AAErB,SAASH,qBACdI,WAAwC,EACxCC,cAA0D,EAC1DC,wBAAsC,EACtCC,SAAiB;IAEjB,MAAMC,kBAAkBF;IACxB,IAAIE,oBAAoB,GAAG;QACzB,OAAOC,QAAQC,OAAO,CAAC,EAAE;IAC3B;IAEA,MAAMC,YAAY,CAAC,IAAI,EAAEP,YAAY,CAAC,EAAEQ,IAAAA,cAAM,KAAI;IAElD,MAAMC,kBAAkB,IAAIJ,QAC1B,CAACC,SAASI;QACR,MAAMC,UAAUC,WAAW;YACzB,MAAMC,UAAUf,gBAAgBgB,GAAG,CAACP;YACpC,IAAIM,WAAWA,QAAQE,SAAS,CAACC,MAAM,GAAG,GAAG;gBAC3CV,QAAQO,QAAQE,SAAS;YAC3B,OAAO;gBACLL,OACE,qBAEC,CAFD,IAAIO,MACF,CAAC,8FAA8F,CAAC,GADlG,qBAAA;2BAAA;gCAAA;kCAAA;gBAEA;YAEJ;YACAnB,gBAAgBoB,MAAM,CAACX;QACzB,GAAGJ;QAEHL,gBAAgBqB,GAAG,CAACZ,WAAW;YAC7BQ,WAAW,EAAE;YACbK,eAAehB;YACfE,SAASA;YACTI;YACAC;QACF;IACF;IAGFV,eAAe;QACboB,MAAMrB;QACNO;IACF;IAEA,OAAOE;AACT;AAEO,SAASZ,0BACdU,SAAiB,EACjBe,IAAO,EACPC,GAAW;IAEX,IAAI,CAACA,KAAK;QACR,MAAM,qBAEL,CAFK,IAAIN,MACR,uEADI,qBAAA;mBAAA;wBAAA;0BAAA;QAEN;IACF;IAEA,MAAMJ,UAAUf,gBAAgBgB,GAAG,CAACP;IACpC,IAAIM,SAAS;QACXA,QAAQE,SAAS,CAACS,IAAI,CAAC;YAAED;YAAKD;QAAK;QACnC,IAAIT,QAAQE,SAAS,CAACC,MAAM,IAAIH,QAAQO,aAAa,EAAE;YACrDK,aAAaZ,QAAQF,OAAO;YAC5BE,QAAQP,OAAO,CAACO,QAAQE,SAAS;YACjCjB,gBAAgBoB,MAAM,CAACX;QACzB;IACF;AACF","ignoreList":[0]}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type { OverlayState } from '../../../../next-devtools/dev-overlay/shared';
|
||||
import type { OriginalStackFramesRequest, OriginalStackFramesResponse } from '../../../../next-devtools/server/shared';
|
||||
type StackFrameResolver = (request: OriginalStackFramesRequest) => Promise<OriginalStackFramesResponse>;
|
||||
export declare function setStackFrameResolver(fn: StackFrameResolver): void;
|
||||
interface StackFrame {
|
||||
file: string;
|
||||
methodName: string;
|
||||
line: number | null;
|
||||
column: number | null;
|
||||
}
|
||||
interface FormattedRuntimeError {
|
||||
type: string;
|
||||
errorName: string;
|
||||
message: string;
|
||||
stack: StackFrame[];
|
||||
}
|
||||
interface FormattedSessionError {
|
||||
url: string;
|
||||
buildError: string | null;
|
||||
runtimeErrors: FormattedRuntimeError[];
|
||||
}
|
||||
interface FormattedConfigError {
|
||||
name: string;
|
||||
message: string;
|
||||
stack: string | null;
|
||||
}
|
||||
export interface FormattedErrorsOutput {
|
||||
configErrors: FormattedConfigError[];
|
||||
sessionErrors: FormattedSessionError[];
|
||||
}
|
||||
export declare function formatErrors(errorsByUrl: Map<string, OverlayState>, nextInstanceErrors?: {
|
||||
nextConfig: unknown[];
|
||||
}): Promise<FormattedErrorsOutput>;
|
||||
export {};
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
0 && (module.exports = {
|
||||
formatErrors: null,
|
||||
setStackFrameResolver: null
|
||||
});
|
||||
function _export(target, all) {
|
||||
for(var name in all)Object.defineProperty(target, name, {
|
||||
enumerable: true,
|
||||
get: all[name]
|
||||
});
|
||||
}
|
||||
_export(exports, {
|
||||
formatErrors: function() {
|
||||
return formatErrors;
|
||||
},
|
||||
setStackFrameResolver: function() {
|
||||
return setStackFrameResolver;
|
||||
}
|
||||
});
|
||||
const _errorsource = require("../../../../shared/lib/error-source");
|
||||
// Dependency injection for stack frame resolver
|
||||
let stackFrameResolver;
|
||||
function setStackFrameResolver(fn) {
|
||||
stackFrameResolver = fn;
|
||||
}
|
||||
async function resolveStackFrames(request) {
|
||||
if (!stackFrameResolver) {
|
||||
throw Object.defineProperty(new Error('Stack frame resolver not initialized. This is a bug in Next.js.'), "__NEXT_ERROR_CODE", {
|
||||
value: "E822",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
return stackFrameResolver(request);
|
||||
}
|
||||
const formatStackFrameToObject = (frame)=>{
|
||||
return {
|
||||
file: frame.file || '<unknown>',
|
||||
methodName: frame.methodName || '<anonymous>',
|
||||
line: frame.line1,
|
||||
column: frame.column1
|
||||
};
|
||||
};
|
||||
const resolveErrorFrames = async (frames, context)=>{
|
||||
try {
|
||||
const resolvedFrames = await resolveStackFrames({
|
||||
frames: frames.map((frame)=>({
|
||||
file: frame.file || null,
|
||||
methodName: frame.methodName || '<anonymous>',
|
||||
arguments: [],
|
||||
line1: frame.line1 || null,
|
||||
column1: frame.column1 || null
|
||||
})),
|
||||
isServer: context.isServer,
|
||||
isEdgeServer: context.isEdgeServer,
|
||||
isAppDirectory: context.isAppDirectory
|
||||
});
|
||||
return resolvedFrames.filter((resolvedFrame)=>{
|
||||
var _resolvedFrame_value_originalStackFrame;
|
||||
return !(resolvedFrame.status === 'fulfilled' && ((_resolvedFrame_value_originalStackFrame = resolvedFrame.value.originalStackFrame) == null ? void 0 : _resolvedFrame_value_originalStackFrame.ignored));
|
||||
}).map((resolvedFrame, j)=>resolvedFrame.status === 'fulfilled' && resolvedFrame.value.originalStackFrame ? formatStackFrameToObject(resolvedFrame.value.originalStackFrame) : formatStackFrameToObject(frames[j]));
|
||||
} catch {
|
||||
return frames.map(formatStackFrameToObject);
|
||||
}
|
||||
};
|
||||
async function formatRuntimeErrorsToObjects(errors, isAppDirectory) {
|
||||
const formattedErrors = [];
|
||||
for (const error of errors){
|
||||
var _error_error, _error_error1, _error_frames;
|
||||
const errorName = ((_error_error = error.error) == null ? void 0 : _error_error.name) || 'Error';
|
||||
const errorMsg = ((_error_error1 = error.error) == null ? void 0 : _error_error1.message) || 'Unknown error';
|
||||
let stack = [];
|
||||
if ((_error_frames = error.frames) == null ? void 0 : _error_frames.length) {
|
||||
const errorSource = (0, _errorsource.getErrorSource)(error.error);
|
||||
stack = await resolveErrorFrames(error.frames, {
|
||||
isServer: errorSource === 'server',
|
||||
isEdgeServer: errorSource === 'edge-server',
|
||||
isAppDirectory
|
||||
});
|
||||
}
|
||||
formattedErrors.push({
|
||||
type: error.type,
|
||||
errorName,
|
||||
message: errorMsg,
|
||||
stack
|
||||
});
|
||||
}
|
||||
return formattedErrors;
|
||||
}
|
||||
async function formatErrors(errorsByUrl, nextInstanceErrors = {
|
||||
nextConfig: []
|
||||
}) {
|
||||
const output = {
|
||||
configErrors: [],
|
||||
sessionErrors: []
|
||||
};
|
||||
// Format Next.js instance errors first (e.g., next.config.js errors)
|
||||
for (const error of nextInstanceErrors.nextConfig){
|
||||
if (error instanceof Error) {
|
||||
output.configErrors.push({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack || null
|
||||
});
|
||||
} else {
|
||||
output.configErrors.push({
|
||||
name: 'Error',
|
||||
message: String(error),
|
||||
stack: null
|
||||
});
|
||||
}
|
||||
}
|
||||
// Format browser session errors
|
||||
for (const [url, overlayState] of errorsByUrl){
|
||||
const totalErrorCount = overlayState.errors.length + (overlayState.buildError ? 1 : 0);
|
||||
if (totalErrorCount === 0) continue;
|
||||
let displayUrl = url;
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
displayUrl = urlObj.pathname + urlObj.search + urlObj.hash;
|
||||
} catch {
|
||||
// If URL parsing fails, use the original URL
|
||||
}
|
||||
const runtimeErrors = await formatRuntimeErrorsToObjects(overlayState.errors, overlayState.routerType === 'app');
|
||||
output.sessionErrors.push({
|
||||
url: displayUrl,
|
||||
buildError: overlayState.buildError || null,
|
||||
runtimeErrors
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=format-errors.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user