@egdesk/next-api-plugin 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generate-helpers.js +117 -26
- package/dist/generate-middleware.js +34 -1
- package/dist/generate-proxy.js +35 -3
- package/dist/setup-userdata.js +12 -4
- package/package.json +1 -1
- package/src/generate-helpers.ts +117 -26
- package/src/generate-middleware.ts +34 -1
- package/src/generate-proxy.ts +35 -3
- package/src/setup-userdata.ts +17 -5
package/dist/generate-helpers.js
CHANGED
|
@@ -50,7 +50,7 @@ function generateHelpers(projectPath) {
|
|
|
50
50
|
const helperContent = `/**
|
|
51
51
|
* EGDesk Helper Functions for Next.js
|
|
52
52
|
*
|
|
53
|
-
* Type-safe helpers for
|
|
53
|
+
* Type-safe helpers for EGDesk user data, FinanceHub, and browser recorder replay.
|
|
54
54
|
* Works in both client and server components.
|
|
55
55
|
*
|
|
56
56
|
* Generated by @egdesk/next-api-plugin
|
|
@@ -58,6 +58,45 @@ function generateHelpers(projectPath) {
|
|
|
58
58
|
|
|
59
59
|
import { EGDESK_CONFIG } from './egdesk.config';
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Parse EGDesk MCP \`/tools/call\` JSON so \`error\` is shown even when HTTP status is 500.
|
|
63
|
+
*/
|
|
64
|
+
async function parseEgdeskMcpToolResponse(response: Response): Promise<any> {
|
|
65
|
+
const result = await response.json().catch(() => null);
|
|
66
|
+
|
|
67
|
+
if (result && typeof result === 'object' && result.success === false) {
|
|
68
|
+
const errMsg =
|
|
69
|
+
typeof result.error === 'string'
|
|
70
|
+
? result.error
|
|
71
|
+
: result.error != null
|
|
72
|
+
? String(result.error)
|
|
73
|
+
: 'Tool call failed';
|
|
74
|
+
throw new Error(errMsg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
let fromBody = '';
|
|
79
|
+
if (result && typeof result === 'object') {
|
|
80
|
+
if (typeof result.error === 'string') fromBody = result.error;
|
|
81
|
+
else if (typeof (result as { message?: string }).message === 'string') {
|
|
82
|
+
fromBody = (result as { message: string }).message;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw new Error(fromBody || \`HTTP \${response.status}: \${response.statusText}\`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!result || result.success !== true) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
result && typeof result === 'object' && typeof result.error === 'string'
|
|
91
|
+
? result.error
|
|
92
|
+
: 'Tool call failed'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const content = result.result?.content?.[0]?.text;
|
|
97
|
+
return content ? JSON.parse(content) : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
/**
|
|
62
101
|
* Call EGDesk user-data MCP tool
|
|
63
102
|
*
|
|
@@ -103,19 +142,7 @@ export async function callUserDataTool(
|
|
|
103
142
|
});
|
|
104
143
|
}
|
|
105
144
|
|
|
106
|
-
|
|
107
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const result = await response.json();
|
|
111
|
-
|
|
112
|
-
if (!result.success) {
|
|
113
|
-
throw new Error(result.error || 'Tool call failed');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Parse MCP response format
|
|
117
|
-
const content = result.result?.content?.[0]?.text;
|
|
118
|
-
return content ? JSON.parse(content) : null;
|
|
145
|
+
return parseEgdeskMcpToolResponse(response);
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
/**
|
|
@@ -329,18 +356,7 @@ export async function callFinanceHubTool(
|
|
|
329
356
|
});
|
|
330
357
|
}
|
|
331
358
|
|
|
332
|
-
|
|
333
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const result = await response.json();
|
|
337
|
-
|
|
338
|
-
if (!result.success) {
|
|
339
|
-
throw new Error(result.error || 'Tool call failed');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const content = result.result?.content?.[0]?.text;
|
|
343
|
-
return content ? JSON.parse(content) : null;
|
|
359
|
+
return parseEgdeskMcpToolResponse(response);
|
|
344
360
|
}
|
|
345
361
|
|
|
346
362
|
/**
|
|
@@ -439,6 +455,81 @@ export async function getOverallStats() {
|
|
|
439
455
|
export async function getSyncHistory(limit: number = 50) {
|
|
440
456
|
return callFinanceHubTool('financehub_get_sync_history', { limit });
|
|
441
457
|
}
|
|
458
|
+
|
|
459
|
+
// ==========================================
|
|
460
|
+
// BROWSER RECORDING HELPERS
|
|
461
|
+
// ==========================================
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Call EGDesk Browser Recording MCP tool (saved recorder tests, replay with optional dates).
|
|
465
|
+
*
|
|
466
|
+
* - Server: \`POST {apiUrl}/browser-recording/tools/call\`
|
|
467
|
+
* - Client: \`POST /__browser_recording_proxy\` (see proxy.ts / middleware)
|
|
468
|
+
*/
|
|
469
|
+
export async function callBrowserRecordingTool(
|
|
470
|
+
toolName: string,
|
|
471
|
+
args: Record<string, any> = {}
|
|
472
|
+
): Promise<any> {
|
|
473
|
+
const body = JSON.stringify({ tool: toolName, arguments: args });
|
|
474
|
+
const headers: Record<string, string> = {
|
|
475
|
+
'Content-Type': 'application/json'
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const isServer = typeof window === 'undefined';
|
|
479
|
+
|
|
480
|
+
let response: Response;
|
|
481
|
+
if (isServer) {
|
|
482
|
+
const apiUrl =
|
|
483
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
|
|
484
|
+
EGDESK_CONFIG.apiUrl;
|
|
485
|
+
const apiKey =
|
|
486
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
|
|
487
|
+
EGDESK_CONFIG.apiKey;
|
|
488
|
+
if (apiKey) {
|
|
489
|
+
headers['X-Api-Key'] = apiKey;
|
|
490
|
+
}
|
|
491
|
+
response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
492
|
+
method: 'POST',
|
|
493
|
+
headers,
|
|
494
|
+
body
|
|
495
|
+
});
|
|
496
|
+
} else {
|
|
497
|
+
response = await fetch('/__browser_recording_proxy', {
|
|
498
|
+
method: 'POST',
|
|
499
|
+
headers,
|
|
500
|
+
body
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return parseEgdeskMcpToolResponse(response);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** List saved *.spec.js files in the EGDesk browser-recorder-tests output folder */
|
|
508
|
+
export async function listBrowserRecordingTests() {
|
|
509
|
+
return callBrowserRecordingTool('browser_recording_list_saved_tests', {});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** Inspect a spec for date-picker replay options */
|
|
513
|
+
export async function getBrowserRecordingReplayOptions(testFile: string) {
|
|
514
|
+
return callBrowserRecordingTool('browser_recording_get_replay_options', { testFile });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export type BrowserRecordingRunOptions = {
|
|
518
|
+
startDate?: string;
|
|
519
|
+
endDate?: string;
|
|
520
|
+
datePickersByIndex?: string[];
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/** Replay a saved recording in Chrome */
|
|
524
|
+
export async function runBrowserRecording(
|
|
525
|
+
testFile: string,
|
|
526
|
+
options: BrowserRecordingRunOptions = {}
|
|
527
|
+
) {
|
|
528
|
+
return callBrowserRecordingTool('browser_recording_run', {
|
|
529
|
+
testFile,
|
|
530
|
+
...options
|
|
531
|
+
});
|
|
532
|
+
}
|
|
442
533
|
`;
|
|
443
534
|
fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
|
|
444
535
|
console.log(`✅ Generated ${helperPath}`);
|
|
@@ -73,7 +73,8 @@ import type { NextRequest } from 'next/server';
|
|
|
73
73
|
/**
|
|
74
74
|
* EGDesk Database Proxy Middleware
|
|
75
75
|
*
|
|
76
|
-
* Intercepts __user_data_proxy requests and forwards them
|
|
76
|
+
* Intercepts __user_data_proxy and __browser_recording_proxy requests and forwards them
|
|
77
|
+
* to the EGDesk MCP HTTP server.
|
|
77
78
|
* This allows CORS-free database access in both local and tunneled environments.
|
|
78
79
|
*
|
|
79
80
|
* Generated by @egdesk/next-api-plugin
|
|
@@ -116,6 +117,38 @@ export async function middleware(request: NextRequest) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
if (pathname.includes('__browser_recording_proxy')) {
|
|
121
|
+
try {
|
|
122
|
+
const body = await request.text();
|
|
123
|
+
|
|
124
|
+
const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
|
|
125
|
+
const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
|
|
126
|
+
|
|
127
|
+
const headers: HeadersInit = {
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (apiKey) {
|
|
132
|
+
headers['X-Api-Key'] = apiKey;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers,
|
|
138
|
+
body,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = await response.json();
|
|
142
|
+
|
|
143
|
+
return NextResponse.json(result, { status: response.status });
|
|
144
|
+
} catch (error: any) {
|
|
145
|
+
return NextResponse.json(
|
|
146
|
+
{ error: 'Proxy error', message: error.message },
|
|
147
|
+
{ status: 500 }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
// Continue to next middleware or route
|
|
120
153
|
return NextResponse.next();
|
|
121
154
|
}
|
package/dist/generate-proxy.js
CHANGED
|
@@ -75,8 +75,8 @@ import type { NextRequest } from 'next/server';
|
|
|
75
75
|
/**
|
|
76
76
|
* EGDesk Database Proxy (Next.js 16+)
|
|
77
77
|
*
|
|
78
|
-
* Intercepts __user_data_proxy requests and forwards them
|
|
79
|
-
* This
|
|
78
|
+
* Intercepts __user_data_proxy and __browser_recording_proxy requests and forwards them
|
|
79
|
+
* to the EGDesk MCP HTTP server. This avoids CORS in local and tunneled environments.
|
|
80
80
|
*
|
|
81
81
|
* Generated by @egdesk/next-api-plugin
|
|
82
82
|
*/
|
|
@@ -118,6 +118,38 @@ export async function proxy(request: NextRequest) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
if (pathname.includes('__browser_recording_proxy')) {
|
|
122
|
+
try {
|
|
123
|
+
const body = await request.text();
|
|
124
|
+
|
|
125
|
+
const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
|
|
126
|
+
const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
|
|
127
|
+
|
|
128
|
+
const headers: HeadersInit = {
|
|
129
|
+
'Content-Type': 'application/json',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (apiKey) {
|
|
133
|
+
headers['X-Api-Key'] = apiKey;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers,
|
|
139
|
+
body,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = await response.json();
|
|
143
|
+
|
|
144
|
+
return NextResponse.json(result, { status: response.status });
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
return NextResponse.json(
|
|
147
|
+
{ error: 'Proxy error', message: error.message },
|
|
148
|
+
{ status: 500 }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
// Continue to next proxy or route
|
|
122
154
|
return NextResponse.next();
|
|
123
155
|
}
|
|
@@ -126,7 +158,7 @@ export const config = {
|
|
|
126
158
|
matcher: [
|
|
127
159
|
/*
|
|
128
160
|
* Match all paths except static assets
|
|
129
|
-
* The proxy function
|
|
161
|
+
* The proxy function filters for __user_data_proxy / __browser_recording_proxy
|
|
130
162
|
*/
|
|
131
163
|
'/:path*',
|
|
132
164
|
],
|
package/dist/setup-userdata.js
CHANGED
|
@@ -66,12 +66,20 @@ async function discoverTables(egdeskUrl = 'http://localhost:8080', apiKey) {
|
|
|
66
66
|
arguments: {}
|
|
67
67
|
})
|
|
68
68
|
});
|
|
69
|
+
const listResult = await listResponse.json().catch(() => null);
|
|
70
|
+
if (listResult && typeof listResult === 'object' && listResult.success === false) {
|
|
71
|
+
throw new Error(listResult.error || 'Failed to list tables');
|
|
72
|
+
}
|
|
69
73
|
if (!listResponse.ok) {
|
|
70
|
-
|
|
74
|
+
const fromBody = listResult && typeof listResult === 'object' && typeof listResult.error === 'string'
|
|
75
|
+
? listResult.error
|
|
76
|
+
: '';
|
|
77
|
+
throw new Error(fromBody || `Failed to list tables: ${listResponse.status} ${listResponse.statusText}`);
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
if (!listResult || !listResult.success) {
|
|
80
|
+
throw new Error(listResult && typeof listResult === 'object' && listResult.error
|
|
81
|
+
? String(listResult.error)
|
|
82
|
+
: 'Failed to list tables');
|
|
75
83
|
}
|
|
76
84
|
// Parse MCP response
|
|
77
85
|
const content = listResult.result?.content?.[0]?.text;
|
package/package.json
CHANGED
package/src/generate-helpers.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function generateHelpers(projectPath: string): void {
|
|
|
17
17
|
const helperContent = `/**
|
|
18
18
|
* EGDesk Helper Functions for Next.js
|
|
19
19
|
*
|
|
20
|
-
* Type-safe helpers for
|
|
20
|
+
* Type-safe helpers for EGDesk user data, FinanceHub, and browser recorder replay.
|
|
21
21
|
* Works in both client and server components.
|
|
22
22
|
*
|
|
23
23
|
* Generated by @egdesk/next-api-plugin
|
|
@@ -25,6 +25,45 @@ export function generateHelpers(projectPath: string): void {
|
|
|
25
25
|
|
|
26
26
|
import { EGDESK_CONFIG } from './egdesk.config';
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Parse EGDesk MCP \`/tools/call\` JSON so \`error\` is shown even when HTTP status is 500.
|
|
30
|
+
*/
|
|
31
|
+
async function parseEgdeskMcpToolResponse(response: Response): Promise<any> {
|
|
32
|
+
const result = await response.json().catch(() => null);
|
|
33
|
+
|
|
34
|
+
if (result && typeof result === 'object' && result.success === false) {
|
|
35
|
+
const errMsg =
|
|
36
|
+
typeof result.error === 'string'
|
|
37
|
+
? result.error
|
|
38
|
+
: result.error != null
|
|
39
|
+
? String(result.error)
|
|
40
|
+
: 'Tool call failed';
|
|
41
|
+
throw new Error(errMsg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
let fromBody = '';
|
|
46
|
+
if (result && typeof result === 'object') {
|
|
47
|
+
if (typeof result.error === 'string') fromBody = result.error;
|
|
48
|
+
else if (typeof (result as { message?: string }).message === 'string') {
|
|
49
|
+
fromBody = (result as { message: string }).message;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(fromBody || \`HTTP \${response.status}: \${response.statusText}\`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!result || result.success !== true) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
result && typeof result === 'object' && typeof result.error === 'string'
|
|
58
|
+
? result.error
|
|
59
|
+
: 'Tool call failed'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const content = result.result?.content?.[0]?.text;
|
|
64
|
+
return content ? JSON.parse(content) : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
28
67
|
/**
|
|
29
68
|
* Call EGDesk user-data MCP tool
|
|
30
69
|
*
|
|
@@ -70,19 +109,7 @@ export async function callUserDataTool(
|
|
|
70
109
|
});
|
|
71
110
|
}
|
|
72
111
|
|
|
73
|
-
|
|
74
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const result = await response.json();
|
|
78
|
-
|
|
79
|
-
if (!result.success) {
|
|
80
|
-
throw new Error(result.error || 'Tool call failed');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Parse MCP response format
|
|
84
|
-
const content = result.result?.content?.[0]?.text;
|
|
85
|
-
return content ? JSON.parse(content) : null;
|
|
112
|
+
return parseEgdeskMcpToolResponse(response);
|
|
86
113
|
}
|
|
87
114
|
|
|
88
115
|
/**
|
|
@@ -296,18 +323,7 @@ export async function callFinanceHubTool(
|
|
|
296
323
|
});
|
|
297
324
|
}
|
|
298
325
|
|
|
299
|
-
|
|
300
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const result = await response.json();
|
|
304
|
-
|
|
305
|
-
if (!result.success) {
|
|
306
|
-
throw new Error(result.error || 'Tool call failed');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const content = result.result?.content?.[0]?.text;
|
|
310
|
-
return content ? JSON.parse(content) : null;
|
|
326
|
+
return parseEgdeskMcpToolResponse(response);
|
|
311
327
|
}
|
|
312
328
|
|
|
313
329
|
/**
|
|
@@ -406,6 +422,81 @@ export async function getOverallStats() {
|
|
|
406
422
|
export async function getSyncHistory(limit: number = 50) {
|
|
407
423
|
return callFinanceHubTool('financehub_get_sync_history', { limit });
|
|
408
424
|
}
|
|
425
|
+
|
|
426
|
+
// ==========================================
|
|
427
|
+
// BROWSER RECORDING HELPERS
|
|
428
|
+
// ==========================================
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Call EGDesk Browser Recording MCP tool (saved recorder tests, replay with optional dates).
|
|
432
|
+
*
|
|
433
|
+
* - Server: \`POST {apiUrl}/browser-recording/tools/call\`
|
|
434
|
+
* - Client: \`POST /__browser_recording_proxy\` (see proxy.ts / middleware)
|
|
435
|
+
*/
|
|
436
|
+
export async function callBrowserRecordingTool(
|
|
437
|
+
toolName: string,
|
|
438
|
+
args: Record<string, any> = {}
|
|
439
|
+
): Promise<any> {
|
|
440
|
+
const body = JSON.stringify({ tool: toolName, arguments: args });
|
|
441
|
+
const headers: Record<string, string> = {
|
|
442
|
+
'Content-Type': 'application/json'
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const isServer = typeof window === 'undefined';
|
|
446
|
+
|
|
447
|
+
let response: Response;
|
|
448
|
+
if (isServer) {
|
|
449
|
+
const apiUrl =
|
|
450
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_URL) ||
|
|
451
|
+
EGDESK_CONFIG.apiUrl;
|
|
452
|
+
const apiKey =
|
|
453
|
+
(typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_EGDESK_API_KEY) ||
|
|
454
|
+
EGDESK_CONFIG.apiKey;
|
|
455
|
+
if (apiKey) {
|
|
456
|
+
headers['X-Api-Key'] = apiKey;
|
|
457
|
+
}
|
|
458
|
+
response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
459
|
+
method: 'POST',
|
|
460
|
+
headers,
|
|
461
|
+
body
|
|
462
|
+
});
|
|
463
|
+
} else {
|
|
464
|
+
response = await fetch('/__browser_recording_proxy', {
|
|
465
|
+
method: 'POST',
|
|
466
|
+
headers,
|
|
467
|
+
body
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return parseEgdeskMcpToolResponse(response);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** List saved *.spec.js files in the EGDesk browser-recorder-tests output folder */
|
|
475
|
+
export async function listBrowserRecordingTests() {
|
|
476
|
+
return callBrowserRecordingTool('browser_recording_list_saved_tests', {});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Inspect a spec for date-picker replay options */
|
|
480
|
+
export async function getBrowserRecordingReplayOptions(testFile: string) {
|
|
481
|
+
return callBrowserRecordingTool('browser_recording_get_replay_options', { testFile });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export type BrowserRecordingRunOptions = {
|
|
485
|
+
startDate?: string;
|
|
486
|
+
endDate?: string;
|
|
487
|
+
datePickersByIndex?: string[];
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/** Replay a saved recording in Chrome */
|
|
491
|
+
export async function runBrowserRecording(
|
|
492
|
+
testFile: string,
|
|
493
|
+
options: BrowserRecordingRunOptions = {}
|
|
494
|
+
) {
|
|
495
|
+
return callBrowserRecordingTool('browser_recording_run', {
|
|
496
|
+
testFile,
|
|
497
|
+
...options
|
|
498
|
+
});
|
|
499
|
+
}
|
|
409
500
|
`;
|
|
410
501
|
|
|
411
502
|
fs.writeFileSync(helperPath, helperContent.replace(/\r?\n/g, os.EOL), 'utf-8');
|
|
@@ -43,7 +43,8 @@ import type { NextRequest } from 'next/server';
|
|
|
43
43
|
/**
|
|
44
44
|
* EGDesk Database Proxy Middleware
|
|
45
45
|
*
|
|
46
|
-
* Intercepts __user_data_proxy requests and forwards them
|
|
46
|
+
* Intercepts __user_data_proxy and __browser_recording_proxy requests and forwards them
|
|
47
|
+
* to the EGDesk MCP HTTP server.
|
|
47
48
|
* This allows CORS-free database access in both local and tunneled environments.
|
|
48
49
|
*
|
|
49
50
|
* Generated by @egdesk/next-api-plugin
|
|
@@ -86,6 +87,38 @@ export async function middleware(request: NextRequest) {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
if (pathname.includes('__browser_recording_proxy')) {
|
|
91
|
+
try {
|
|
92
|
+
const body = await request.text();
|
|
93
|
+
|
|
94
|
+
const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
|
|
95
|
+
const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
|
|
96
|
+
|
|
97
|
+
const headers: HeadersInit = {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (apiKey) {
|
|
102
|
+
headers['X-Api-Key'] = apiKey;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers,
|
|
108
|
+
body,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const result = await response.json();
|
|
112
|
+
|
|
113
|
+
return NextResponse.json(result, { status: response.status });
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
return NextResponse.json(
|
|
116
|
+
{ error: 'Proxy error', message: error.message },
|
|
117
|
+
{ status: 500 }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
89
122
|
// Continue to next middleware or route
|
|
90
123
|
return NextResponse.next();
|
|
91
124
|
}
|
package/src/generate-proxy.ts
CHANGED
|
@@ -45,8 +45,8 @@ import type { NextRequest } from 'next/server';
|
|
|
45
45
|
/**
|
|
46
46
|
* EGDesk Database Proxy (Next.js 16+)
|
|
47
47
|
*
|
|
48
|
-
* Intercepts __user_data_proxy requests and forwards them
|
|
49
|
-
* This
|
|
48
|
+
* Intercepts __user_data_proxy and __browser_recording_proxy requests and forwards them
|
|
49
|
+
* to the EGDesk MCP HTTP server. This avoids CORS in local and tunneled environments.
|
|
50
50
|
*
|
|
51
51
|
* Generated by @egdesk/next-api-plugin
|
|
52
52
|
*/
|
|
@@ -88,6 +88,38 @@ export async function proxy(request: NextRequest) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
if (pathname.includes('__browser_recording_proxy')) {
|
|
92
|
+
try {
|
|
93
|
+
const body = await request.text();
|
|
94
|
+
|
|
95
|
+
const apiKey = process.env.NEXT_PUBLIC_EGDESK_API_KEY;
|
|
96
|
+
const apiUrl = process.env.NEXT_PUBLIC_EGDESK_API_URL || 'http://localhost:8080';
|
|
97
|
+
|
|
98
|
+
const headers: HeadersInit = {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (apiKey) {
|
|
103
|
+
headers['X-Api-Key'] = apiKey;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const response = await fetch(\`\${apiUrl}/browser-recording/tools/call\`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers,
|
|
109
|
+
body,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await response.json();
|
|
113
|
+
|
|
114
|
+
return NextResponse.json(result, { status: response.status });
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
return NextResponse.json(
|
|
117
|
+
{ error: 'Proxy error', message: error.message },
|
|
118
|
+
{ status: 500 }
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
91
123
|
// Continue to next proxy or route
|
|
92
124
|
return NextResponse.next();
|
|
93
125
|
}
|
|
@@ -96,7 +128,7 @@ export const config = {
|
|
|
96
128
|
matcher: [
|
|
97
129
|
/*
|
|
98
130
|
* Match all paths except static assets
|
|
99
|
-
* The proxy function
|
|
131
|
+
* The proxy function filters for __user_data_proxy / __browser_recording_proxy
|
|
100
132
|
*/
|
|
101
133
|
'/:path*',
|
|
102
134
|
],
|
package/src/setup-userdata.ts
CHANGED
|
@@ -53,14 +53,26 @@ export async function discoverTables(
|
|
|
53
53
|
})
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const listResult = await listResponse.json().catch(() => null);
|
|
57
|
+
|
|
58
|
+
if (listResult && typeof listResult === 'object' && listResult.success === false) {
|
|
59
|
+
throw new Error(listResult.error || 'Failed to list tables');
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
if (!listResponse.ok) {
|
|
63
|
+
const fromBody =
|
|
64
|
+
listResult && typeof listResult === 'object' && typeof listResult.error === 'string'
|
|
65
|
+
? listResult.error
|
|
66
|
+
: '';
|
|
67
|
+
throw new Error(fromBody || `Failed to list tables: ${listResponse.status} ${listResponse.statusText}`);
|
|
68
|
+
}
|
|
61
69
|
|
|
62
|
-
if (!listResult.success) {
|
|
63
|
-
throw new Error(
|
|
70
|
+
if (!listResult || !listResult.success) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
listResult && typeof listResult === 'object' && listResult.error
|
|
73
|
+
? String(listResult.error)
|
|
74
|
+
: 'Failed to list tables'
|
|
75
|
+
);
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
// Parse MCP response
|