easyeda-mcp-pro 0.2.0 → 0.3.0
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/LICENSE +21 -0
- package/README.md +64 -19
- package/dist/bridge/manager.d.ts +18 -11
- package/dist/bridge/manager.js +164 -113
- package/dist/bridge/manager.js.map +1 -1
- package/dist/bridge/protocol.d.ts +2 -4
- package/dist/bridge/protocol.js +2 -0
- package/dist/bridge/protocol.js.map +1 -1
- package/dist/bridge/types.d.ts +11 -42
- package/dist/bridge/types.js +3 -0
- package/dist/bridge/types.js.map +1 -1
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.js +1 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/profiles.js +6 -6
- package/dist/config/profiles.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +22 -64
- package/dist/index.js.map +1 -1
- package/dist/schemas/common.d.ts +1 -108
- package/dist/schemas/common.js +0 -62
- package/dist/schemas/common.js.map +1 -1
- package/dist/server/factory.d.ts +6 -3
- package/dist/server/factory.js +18 -12
- package/dist/server/factory.js.map +1 -1
- package/dist/server/transports/http.js +83 -0
- package/dist/server/transports/http.js.map +1 -1
- package/dist/storage/database.js +5 -4
- package/dist/storage/database.js.map +1 -1
- package/dist/storage/types.d.ts +0 -9
- package/dist/storage/types.js +1 -8
- package/dist/storage/types.js.map +1 -1
- package/dist/tools/L0_diagnostics.js +28 -7
- package/dist/tools/L0_diagnostics.js.map +1 -1
- package/dist/tools/L1_board.js +43 -15
- package/dist/tools/L1_board.js.map +1 -1
- package/dist/tools/L1_bom.js +18 -0
- package/dist/tools/L1_bom.js.map +1 -1
- package/dist/tools/register.d.ts +3 -0
- package/dist/tools/register.js +15 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/registry.js +17 -16
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/types.d.ts +2 -21
- package/dist/vendors/jlcpcb/client.d.ts +3 -2
- package/dist/vendors/lcsc/client.d.ts +0 -9
- package/dist/vendors/lcsc/client.js.map +1 -1
- package/dist/vendors/mouser/client.d.ts +2 -5
- package/dist/vendors/mouser/client.js.map +1 -1
- package/package.json +7 -5
- package/dist/bridge/client.d.ts +0 -16
- package/dist/bridge/client.js +0 -48
- package/dist/bridge/client.js.map +0 -1
- package/dist/bridge/extension.d.ts +0 -26
- package/dist/bridge/extension.js +0 -556
- package/dist/bridge/extension.js.map +0 -1
- package/dist/server/errors.d.ts +0 -3
- package/dist/server/errors.js +0 -59
- package/dist/server/errors.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 oaslananka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ The server connects to EasyEDA Pro via a WebSocket bridge extension, enabling re
|
|
|
42
42
|
|
|
43
43
|
- **Node.js** >=24 <27 (required for the latest JavaScript features)
|
|
44
44
|
- **pnpm** >=11 (for local development; the npm package is self-contained)
|
|
45
|
-
- **EasyEDA Pro** with the
|
|
45
|
+
- **EasyEDA Pro** with the bundled bridge extension installed and running
|
|
46
46
|
- For supplier integration: API credentials from JLCPCB, LCSC, Mouser, or DigiKey
|
|
47
47
|
|
|
48
48
|
---
|
|
@@ -70,6 +70,7 @@ cd easyeda-mcp-pro
|
|
|
70
70
|
cp .env.example .env
|
|
71
71
|
pnpm install
|
|
72
72
|
pnpm build
|
|
73
|
+
pnpm build:extension
|
|
73
74
|
pnpm start
|
|
74
75
|
```
|
|
75
76
|
|
|
@@ -90,11 +91,14 @@ Copy `.env.example` to `.env` and edit. All variables have safe defaults — onl
|
|
|
90
91
|
|
|
91
92
|
### Bridge (EasyEDA Pro connection)
|
|
92
93
|
|
|
93
|
-
| Variable
|
|
94
|
-
|
|
|
95
|
-
| `BRIDGE_HOST`
|
|
96
|
-
| `BRIDGE_PORT`
|
|
97
|
-
| `
|
|
94
|
+
| Variable | Default | Description |
|
|
95
|
+
| --------------------- | ------------------- | -------------------------------- |
|
|
96
|
+
| `BRIDGE_HOST` | `127.0.0.1` | Bridge WebSocket host |
|
|
97
|
+
| `BRIDGE_PORT` | `18601` | Primary bridge port |
|
|
98
|
+
| `BRIDGE_PORT_SCAN` | `18601,49620-49629` | Port scan spec (comma/range) |
|
|
99
|
+
| `BRIDGE_TIMEOUT_MS` | `15000` | Bridge call timeout (ms) |
|
|
100
|
+
| `BRIDGE_HEARTBEAT_MS` | `10000` | Heartbeat interval (ms) |
|
|
101
|
+
| `BRIDGE_TOKEN` | `''` | Session token for extension auth |
|
|
98
102
|
|
|
99
103
|
### Storage
|
|
100
104
|
|
|
@@ -124,15 +128,36 @@ Configure an AI provider for LLM-assisted design review:
|
|
|
124
128
|
|
|
125
129
|
When using `TRANSPORT=http`:
|
|
126
130
|
|
|
127
|
-
| Variable
|
|
128
|
-
|
|
|
129
|
-
| `HTTP_HOST`
|
|
130
|
-
| `HTTP_PORT`
|
|
131
|
-
| `
|
|
131
|
+
| Variable | Default | Description |
|
|
132
|
+
| --------------------- | ----------- | ----------------------------------------- |
|
|
133
|
+
| `HTTP_HOST` | `127.0.0.1` | Bind address (use `0.0.0.0` with caution) |
|
|
134
|
+
| `HTTP_PORT` | `3000` | Port |
|
|
135
|
+
| `HTTP_RATE_LIMIT_MAX` | `100` | Max requests per minute per IP |
|
|
136
|
+
| `CORS_ORIGIN` | `''` | Allowed CORS origin |
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
#### Production HTTP Security
|
|
134
139
|
|
|
135
|
-
|
|
140
|
+
For remote HTTP deployments, OAuth 2.0 / OpenID Connect is strongly recommended:
|
|
141
|
+
|
|
142
|
+
| Variable | Default | Description |
|
|
143
|
+
| ----------------------- | ----------------- | -------------------------------------------- |
|
|
144
|
+
| `OAUTH_ENABLED` | `false` | Enable Bearer token validation |
|
|
145
|
+
| `OAUTH_ISSUER` | `''` | Expected token issuer (`iss` claim) |
|
|
146
|
+
| `OAUTH_AUDIENCE` | `easyeda-mcp-pro` | Expected token audience (`aud` claim) |
|
|
147
|
+
| `OAUTH_JWKS_URI` | `''` | JWKS endpoint for token signature validation |
|
|
148
|
+
| `OAUTH_REQUIRED_SCOPES` | `easyeda:read` | Required token scope |
|
|
149
|
+
|
|
150
|
+
When `OAUTH_ENABLED=true`, every request to `/mcp` must include an `Authorization: Bearer <token>` header. Tokens are validated against the JWKS endpoint if configured, or structurally validated otherwise.
|
|
151
|
+
|
|
152
|
+
The server enforces a safety check at startup: **non-loopback `HTTP_HOST` without OAuth is rejected**.
|
|
153
|
+
|
|
154
|
+
#### HTTP Security Features
|
|
155
|
+
|
|
156
|
+
- **Rate limiting**: Per-IP sliding window (configurable via `HTTP_RATE_LIMIT_MAX`), returns `429 Too Many Requests` with retry-after header
|
|
157
|
+
- **Security headers**: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection: 0`, `Referrer-Policy: strict-origin-when-cross-origin`
|
|
158
|
+
- **Health endpoints**: `/healthz` (liveness) and `/readyz` (readiness) return JSON status
|
|
159
|
+
|
|
160
|
+
See `.env.example` for the complete list of configuration variables.
|
|
136
161
|
|
|
137
162
|
---
|
|
138
163
|
|
|
@@ -228,6 +253,19 @@ All 25 tools are available via the MCP server. Tools are filtered by the active
|
|
|
228
253
|
- **stdio** (default): Standard MCP transport — works with Claude Desktop, Cursor, and most MCP clients
|
|
229
254
|
- **HTTP**: Streamable HTTP transport with `/healthz`, `/readyz`, `/mcp` endpoints, CORS, and optional OAuth — suitable for remote deployments
|
|
230
255
|
|
|
256
|
+
### Bridge extension
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pnpm build:extension
|
|
260
|
+
pnpm verify:extension
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The extension build writes `easyeda-bridge-extension.eext` at the repository root.
|
|
264
|
+
It contains `extension.json`, the bundled browser script, and the image assets
|
|
265
|
+
required by EasyEDA Pro.
|
|
266
|
+
|
|
267
|
+
Installation: Open EasyEDA Pro → **Settings** → **Extensions** → **Extension Manager...** → **Import Extension**, then select the `.eext` file. Make sure **Allow External Interaction** is enabled for the extension.
|
|
268
|
+
|
|
231
269
|
---
|
|
232
270
|
|
|
233
271
|
## Development
|
|
@@ -243,11 +281,13 @@ pnpm typecheck # TypeScript
|
|
|
243
281
|
pnpm lint # ESLint
|
|
244
282
|
|
|
245
283
|
# Test
|
|
246
|
-
pnpm test # Vitest (
|
|
284
|
+
pnpm test # Vitest (90 tests across 12 files)
|
|
247
285
|
pnpm test:coverage # With coverage report
|
|
248
286
|
|
|
249
287
|
# Build & run
|
|
250
288
|
pnpm build # tsc -> dist/
|
|
289
|
+
pnpm build:extension # Bundle EasyEDA Pro extension
|
|
290
|
+
pnpm verify:extension # Verify extension package contents
|
|
251
291
|
pnpm dev # Hot-reload dev mode
|
|
252
292
|
pnpm start # Run compiled build
|
|
253
293
|
|
|
@@ -259,9 +299,9 @@ pnpm inspector
|
|
|
259
299
|
|
|
260
300
|
```
|
|
261
301
|
src/
|
|
262
|
-
├── index.ts # Entry point
|
|
302
|
+
├── index.ts # Entry point (stdio or HTTP)
|
|
263
303
|
├── bridge/ # EasyEDA Pro WebSocket bridge protocol
|
|
264
|
-
│ ├── manager.ts,
|
|
304
|
+
│ ├── manager.ts, protocol.ts, types.ts
|
|
265
305
|
├── config/ # Environment configuration
|
|
266
306
|
│ ├── env.ts, profiles.ts, feature-flags.ts
|
|
267
307
|
├── schemas/ # Shared Zod schemas
|
|
@@ -269,13 +309,15 @@ src/
|
|
|
269
309
|
│ ├── factory.ts, errors.ts
|
|
270
310
|
│ └── transports/
|
|
271
311
|
│ └── http.ts # HTTP/Streamable HTTP transport
|
|
272
|
-
├── storage/ #
|
|
312
|
+
├── storage/ # Node.js sqlite storage (cache, artifacts)
|
|
273
313
|
├── tools/ # 25 MCP tool definitions (6 groups)
|
|
274
|
-
│ ├── registry.ts, types.ts
|
|
314
|
+
│ ├── register.ts, registry.ts, types.ts
|
|
275
315
|
│ ├── L0_diagnostics.ts, L1_schematic.ts, L1_bom.ts
|
|
276
316
|
│ ├── L1_drc_erc.ts, L1_board.ts, L1_export.ts
|
|
277
317
|
└── vendors/ # Supplier API clients
|
|
278
318
|
├── lcsc/, jlcpcb/, mouser/, digikey/
|
|
319
|
+
|
|
320
|
+
easyeda-bridge-extension/ # EasyEDA Pro bridge extension workspace package
|
|
279
321
|
```
|
|
280
322
|
|
|
281
323
|
---
|
|
@@ -283,10 +325,14 @@ src/
|
|
|
283
325
|
## Security
|
|
284
326
|
|
|
285
327
|
- **Production safety**: Validates config at startup — rejects non-loopback HTTP without OAuth, blocks dangerous features in production
|
|
328
|
+
- **OAuth/JWKS**: Optional Bearer token validation via JWKS endpoint for HTTP transport
|
|
329
|
+
- **Rate limiting**: Per-IP sliding window rate limiter on HTTP transport (default 100 req/min)
|
|
330
|
+
- **Path traversal protection**: All file export paths validated against `ARTIFACT_DIR`
|
|
286
331
|
- **Secret redaction**: API keys, tokens, passwords are redacted from logs and diagnostic output
|
|
287
332
|
- **Branch protection**: `main` branch requires review and up-to-date status checks
|
|
288
333
|
- **Code scanning**: CodeQL analysis runs on every push and PR (security-extended + security-and-quality queries)
|
|
289
334
|
- **Dependency management**: Renovate automatically updates dependencies with security patches
|
|
335
|
+
- **Supply-chain hygiene**: pnpm workspace build, pinned GitHub Actions, and no native SQLite addon dependency
|
|
290
336
|
- **Reporting**: See [SECURITY.md](SECURITY.md) for vulnerability disclosure
|
|
291
337
|
|
|
292
338
|
---
|
|
@@ -299,6 +345,5 @@ src/
|
|
|
299
345
|
|
|
300
346
|
## Related
|
|
301
347
|
|
|
302
|
-
- [easyeda-bridge](https://github.com/oaslananka/easyeda-bridge) — EasyEDA Pro plugin that exposes the bridge WebSocket API
|
|
303
348
|
- [Model Context Protocol](https://modelcontextprotocol.io) — Standard protocol for AI tool integration
|
|
304
349
|
- [EasyEDA Pro](https://pro.easyeda.com) — Professional PCB design tool
|
package/dist/bridge/manager.d.ts
CHANGED
|
@@ -2,36 +2,43 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { type EnvConfig } from '../config/env.js';
|
|
3
3
|
import { type BridgeHello } from './protocol.js';
|
|
4
4
|
export type BridgeState = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
5
|
-
export interface BridgeEvents {
|
|
6
|
-
stateChanged: (state: BridgeState, previous: BridgeState) => void;
|
|
7
|
-
connected: (hello: BridgeHello) => void;
|
|
8
|
-
disconnected: (reason: string) => void;
|
|
9
|
-
error: (error: Error) => void;
|
|
10
|
-
heartbeat: (timestamp: number) => void;
|
|
11
|
-
}
|
|
12
5
|
export declare class BridgeManager extends EventEmitter {
|
|
13
6
|
state: BridgeState;
|
|
14
7
|
hello: BridgeHello | null;
|
|
8
|
+
private wss;
|
|
15
9
|
private ws;
|
|
16
10
|
private heartbeatTimer;
|
|
17
11
|
private reconnectTimer;
|
|
12
|
+
private _activePort;
|
|
13
|
+
private _connectedAtMs;
|
|
18
14
|
private requestMap;
|
|
19
15
|
private requestIdCounter;
|
|
20
16
|
private config;
|
|
21
17
|
private _lastHeartbeatMs;
|
|
22
18
|
constructor(config: EnvConfig);
|
|
23
19
|
get lastHeartbeatMs(): number;
|
|
20
|
+
get activePort(): number;
|
|
21
|
+
get uptimeMs(): number;
|
|
24
22
|
get connected(): boolean;
|
|
25
23
|
connect(): Promise<void>;
|
|
24
|
+
private tryListen;
|
|
25
|
+
private handleConnection;
|
|
26
26
|
call<TParams, TResult>(method: string, params?: TParams, opts?: {
|
|
27
27
|
timeoutMs?: number;
|
|
28
28
|
traceparent?: string;
|
|
29
29
|
}): Promise<TResult>;
|
|
30
30
|
disconnect(reason?: string): void;
|
|
31
|
-
private tryConnect;
|
|
32
|
-
private sendHandshake;
|
|
33
|
-
private handleHello;
|
|
34
31
|
private handleResponse;
|
|
32
|
+
private rejectPending;
|
|
35
33
|
private startHeartbeat;
|
|
36
|
-
private
|
|
34
|
+
private stopHeartbeat;
|
|
37
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse a port scan specification string into an ordered array of ports.
|
|
38
|
+
* Supports comma-separated values and dash ranges:
|
|
39
|
+
* "18601" → [18601]
|
|
40
|
+
* "18601,49620" → [18601, 49620]
|
|
41
|
+
* "49620-49629" → [49620, 49621, ..., 49629]
|
|
42
|
+
* "18601,49620-49629" → [18601, 49620, 49621, ..., 49629]
|
|
43
|
+
*/
|
|
44
|
+
export declare function parsePortScanSpec(spec: string): number[];
|
package/dist/bridge/manager.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { WebSocketServer } from 'ws';
|
|
2
3
|
import { getLogger } from '../utils/logger.js';
|
|
3
|
-
import {
|
|
4
|
+
import { BridgeHandshakeSchema } from './protocol.js';
|
|
5
|
+
import { EasyedaApiMethodSchema } from './types.js';
|
|
4
6
|
export class BridgeManager extends EventEmitter {
|
|
5
7
|
state = 'disconnected';
|
|
6
8
|
hello = null;
|
|
9
|
+
wss = null;
|
|
7
10
|
ws = null;
|
|
8
11
|
heartbeatTimer = null;
|
|
9
12
|
reconnectTimer = null;
|
|
13
|
+
_activePort = 0;
|
|
14
|
+
_connectedAtMs = 0;
|
|
10
15
|
requestMap = new Map();
|
|
11
16
|
requestIdCounter = 0;
|
|
12
17
|
config;
|
|
@@ -18,6 +23,14 @@ export class BridgeManager extends EventEmitter {
|
|
|
18
23
|
get lastHeartbeatMs() {
|
|
19
24
|
return this._lastHeartbeatMs;
|
|
20
25
|
}
|
|
26
|
+
get activePort() {
|
|
27
|
+
return this._activePort;
|
|
28
|
+
}
|
|
29
|
+
get uptimeMs() {
|
|
30
|
+
if (this.state !== 'connected' || this._connectedAtMs === 0)
|
|
31
|
+
return 0;
|
|
32
|
+
return Date.now() - this._connectedAtMs;
|
|
33
|
+
}
|
|
21
34
|
get connected() {
|
|
22
35
|
return this.state === 'connected';
|
|
23
36
|
}
|
|
@@ -27,22 +40,113 @@ export class BridgeManager extends EventEmitter {
|
|
|
27
40
|
const prev = this.state;
|
|
28
41
|
this.state = 'connecting';
|
|
29
42
|
this.emit('stateChanged', this.state, prev);
|
|
30
|
-
const ports = this.parsePortScan();
|
|
31
43
|
const logger = getLogger();
|
|
44
|
+
const ports = parsePortScanSpec(this.config.BRIDGE_PORT_SCAN);
|
|
45
|
+
let lastErr = null;
|
|
32
46
|
for (const port of ports) {
|
|
33
47
|
try {
|
|
34
|
-
await this.
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
await this.tryListen(port);
|
|
49
|
+
logger.info({ port }, 'bridge websocket server listening');
|
|
50
|
+
return;
|
|
37
51
|
}
|
|
38
|
-
catch {
|
|
39
|
-
|
|
52
|
+
catch (err) {
|
|
53
|
+
lastErr = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
logger.debug({ port, err: lastErr.message }, 'bridge port unavailable, trying next');
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
|
-
|
|
43
|
-
this.state = '
|
|
44
|
-
this.emit('stateChanged',
|
|
45
|
-
|
|
57
|
+
logger.error({ ports, err: lastErr?.message }, 'failed to start bridge server on any port');
|
|
58
|
+
this.state = 'error';
|
|
59
|
+
this.emit('stateChanged', 'error', 'connecting');
|
|
60
|
+
this.emit('error', lastErr ?? new Error('No available bridge port'));
|
|
61
|
+
throw lastErr ?? new Error('No available bridge port');
|
|
62
|
+
}
|
|
63
|
+
tryListen(port) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const wss = new WebSocketServer({
|
|
66
|
+
host: this.config.BRIDGE_HOST,
|
|
67
|
+
port,
|
|
68
|
+
});
|
|
69
|
+
this.wss = wss;
|
|
70
|
+
this._activePort = port;
|
|
71
|
+
wss.once('listening', () => resolve());
|
|
72
|
+
wss.once('error', (err) => {
|
|
73
|
+
this.wss = null;
|
|
74
|
+
this._activePort = 0;
|
|
75
|
+
reject(err);
|
|
76
|
+
});
|
|
77
|
+
wss.on('error', (err) => {
|
|
78
|
+
getLogger().error({ err }, 'bridge server error');
|
|
79
|
+
this.emit('error', err);
|
|
80
|
+
});
|
|
81
|
+
wss.on('connection', (ws) => this.handleConnection(ws));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
handleConnection(ws) {
|
|
85
|
+
const logger = getLogger();
|
|
86
|
+
logger.info('new bridge client connection');
|
|
87
|
+
// Only allow one connected client at a time
|
|
88
|
+
if (this.ws) {
|
|
89
|
+
logger.warn('rejecting concurrent bridge connection');
|
|
90
|
+
ws.close(4000, 'Only one client allowed');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
ws.on('message', (raw) => {
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(raw.toString());
|
|
96
|
+
if (data.type === 'handshake') {
|
|
97
|
+
const parsed = BridgeHandshakeSchema.safeParse(data);
|
|
98
|
+
if (!parsed.success) {
|
|
99
|
+
logger.warn('bridge handshake validation failed');
|
|
100
|
+
ws.close(4001, 'Invalid handshake');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (this.config.BRIDGE_TOKEN && parsed.data.sessionToken !== this.config.BRIDGE_TOKEN) {
|
|
104
|
+
logger.warn('bridge handshake rejected: invalid session token');
|
|
105
|
+
ws.close(4001, 'Invalid session token');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.ws = ws;
|
|
109
|
+
this.state = 'connected';
|
|
110
|
+
this._connectedAtMs = Date.now();
|
|
111
|
+
this.hello = {
|
|
112
|
+
type: 'hello',
|
|
113
|
+
bridgeVersion: '1.0.0',
|
|
114
|
+
easyedaVersion: parsed.data.easyedaVersion,
|
|
115
|
+
capabilities: EasyedaApiMethodSchema.options,
|
|
116
|
+
methodRegistryHash: 'bridge-1.0.0',
|
|
117
|
+
devMode: parsed.data.devMode ?? false,
|
|
118
|
+
};
|
|
119
|
+
// Reply with hello
|
|
120
|
+
ws.send(JSON.stringify(this.hello));
|
|
121
|
+
this.emit('stateChanged', 'connected', 'connecting');
|
|
122
|
+
this.emit('connected', this.hello);
|
|
123
|
+
this.startHeartbeat();
|
|
124
|
+
}
|
|
125
|
+
else if (data.type === 'response') {
|
|
126
|
+
this.handleResponse(data);
|
|
127
|
+
}
|
|
128
|
+
else if (data.type === 'heartbeat') {
|
|
129
|
+
this._lastHeartbeatMs = Date.now();
|
|
130
|
+
this.emit('heartbeat', data.timestamp);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
logger.error({ err }, 'bridge message processing error');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
ws.on('close', (code, reason) => {
|
|
138
|
+
logger.info({ code, reason: reason.toString() }, 'bridge client disconnected');
|
|
139
|
+
this.ws = null;
|
|
140
|
+
const prevState = this.state;
|
|
141
|
+
this.state = 'connecting';
|
|
142
|
+
this.rejectPending(new Error(`Bridge disconnected: ${reason.toString() || 'connection closed'}`));
|
|
143
|
+
this.emit('stateChanged', this.state, prevState);
|
|
144
|
+
this.emit('disconnected', reason.toString() || 'connection closed');
|
|
145
|
+
this.stopHeartbeat();
|
|
146
|
+
});
|
|
147
|
+
ws.on('error', (err) => {
|
|
148
|
+
logger.error({ err }, 'bridge client socket error');
|
|
149
|
+
});
|
|
46
150
|
}
|
|
47
151
|
async call(method, params, opts) {
|
|
48
152
|
const ws = this.ws;
|
|
@@ -76,101 +180,20 @@ export class BridgeManager extends EventEmitter {
|
|
|
76
180
|
disconnect(reason) {
|
|
77
181
|
const prev = this.state;
|
|
78
182
|
this.state = 'disconnected';
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.heartbeatTimer = null;
|
|
82
|
-
}
|
|
83
|
-
if (this.reconnectTimer) {
|
|
84
|
-
clearTimeout(this.reconnectTimer);
|
|
85
|
-
this.reconnectTimer = null;
|
|
86
|
-
}
|
|
87
|
-
for (const [, entry] of this.requestMap) {
|
|
88
|
-
clearTimeout(entry.timer);
|
|
89
|
-
entry.reject(new Error(`Bridge disconnected: ${reason ?? 'unknown'}`));
|
|
90
|
-
}
|
|
91
|
-
this.requestMap.clear();
|
|
183
|
+
this.stopHeartbeat();
|
|
184
|
+
this.rejectPending(new Error(`Bridge disconnected: ${reason ?? 'unknown'}`));
|
|
92
185
|
if (this.ws) {
|
|
93
186
|
this.ws.close();
|
|
94
187
|
this.ws = null;
|
|
95
188
|
}
|
|
189
|
+
if (this.wss) {
|
|
190
|
+
this.wss.close();
|
|
191
|
+
this.wss = null;
|
|
192
|
+
}
|
|
96
193
|
this.hello = null;
|
|
97
194
|
this.emit('stateChanged', this.state, prev);
|
|
98
195
|
this.emit('disconnected', reason ?? 'unknown');
|
|
99
196
|
}
|
|
100
|
-
async tryConnect(port) {
|
|
101
|
-
const logger = getLogger();
|
|
102
|
-
const url = `ws://${this.config.BRIDGE_HOST}:${port}`;
|
|
103
|
-
logger.debug({ url }, 'trying bridge connection');
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const ws = new WebSocket(url);
|
|
106
|
-
const timeout = setTimeout(() => {
|
|
107
|
-
ws.close();
|
|
108
|
-
reject(new Error(`Connection timeout for ${url}`));
|
|
109
|
-
}, 5000);
|
|
110
|
-
ws.onopen = () => {
|
|
111
|
-
clearTimeout(timeout);
|
|
112
|
-
this.ws = ws;
|
|
113
|
-
this.sendHandshake();
|
|
114
|
-
};
|
|
115
|
-
ws.onmessage = (event) => {
|
|
116
|
-
try {
|
|
117
|
-
const data = JSON.parse(event.data);
|
|
118
|
-
if (data.type === 'hello') {
|
|
119
|
-
const parsed = BridgeHelloSchema.parse(data);
|
|
120
|
-
this.handleHello(parsed, port);
|
|
121
|
-
resolve();
|
|
122
|
-
}
|
|
123
|
-
else if (data.type === 'response') {
|
|
124
|
-
this.handleResponse(data);
|
|
125
|
-
}
|
|
126
|
-
else if (data.type === 'heartbeat') {
|
|
127
|
-
this._lastHeartbeatMs = Date.now();
|
|
128
|
-
}
|
|
129
|
-
else if (data.type === 'error') {
|
|
130
|
-
logger.error({ data }, 'bridge error message');
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
logger.error({ err, raw: event.data }, 'bridge message parse error');
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
ws.onerror = (err) => {
|
|
138
|
-
clearTimeout(timeout);
|
|
139
|
-
reject(err instanceof Error ? err : new Error('WebSocket error'));
|
|
140
|
-
};
|
|
141
|
-
ws.onclose = () => {
|
|
142
|
-
clearTimeout(timeout);
|
|
143
|
-
if (this.state === 'connected') {
|
|
144
|
-
this.disconnect('connection closed');
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
sendHandshake() {
|
|
150
|
-
const handshake = {
|
|
151
|
-
type: 'handshake',
|
|
152
|
-
protocol: 'easyeda-mcp-pro.bridge',
|
|
153
|
-
protocolVersion: '1.0.0',
|
|
154
|
-
clientName: 'easyeda-mcp-pro',
|
|
155
|
-
sessionToken: this.config.BRIDGE_TOKEN || undefined,
|
|
156
|
-
};
|
|
157
|
-
this.ws?.send(JSON.stringify(handshake));
|
|
158
|
-
}
|
|
159
|
-
handleHello(hello, port) {
|
|
160
|
-
const logger = getLogger();
|
|
161
|
-
const prev = this.state;
|
|
162
|
-
this.state = 'connected';
|
|
163
|
-
this.hello = hello;
|
|
164
|
-
logger.info({
|
|
165
|
-
bridgeVersion: hello.bridgeVersion,
|
|
166
|
-
easyedaVersion: hello.easyedaVersion,
|
|
167
|
-
capabilities: hello.capabilities,
|
|
168
|
-
port,
|
|
169
|
-
}, 'bridge connected');
|
|
170
|
-
this.emit('stateChanged', this.state, prev);
|
|
171
|
-
this.emit('connected', hello);
|
|
172
|
-
this.startHeartbeat();
|
|
173
|
-
}
|
|
174
197
|
handleResponse(data) {
|
|
175
198
|
const entry = this.requestMap.get(data.id);
|
|
176
199
|
if (!entry) {
|
|
@@ -187,6 +210,13 @@ export class BridgeManager extends EventEmitter {
|
|
|
187
210
|
entry.reject(error);
|
|
188
211
|
}
|
|
189
212
|
}
|
|
213
|
+
rejectPending(error) {
|
|
214
|
+
for (const [, entry] of this.requestMap) {
|
|
215
|
+
clearTimeout(entry.timer);
|
|
216
|
+
entry.reject(error);
|
|
217
|
+
}
|
|
218
|
+
this.requestMap.clear();
|
|
219
|
+
}
|
|
190
220
|
startHeartbeat() {
|
|
191
221
|
if (this.heartbeatTimer)
|
|
192
222
|
clearInterval(this.heartbeatTimer);
|
|
@@ -196,30 +226,51 @@ export class BridgeManager extends EventEmitter {
|
|
|
196
226
|
this.ws.send(JSON.stringify({ type: 'heartbeat', timestamp: Date.now() }));
|
|
197
227
|
}
|
|
198
228
|
catch {
|
|
229
|
+
getLogger().warn('bridge heartbeat send failed, disconnecting');
|
|
199
230
|
this.disconnect('heartbeat failed');
|
|
200
231
|
}
|
|
201
232
|
}
|
|
202
233
|
}, this.config.BRIDGE_HEARTBEAT_MS);
|
|
203
234
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
stopHeartbeat() {
|
|
236
|
+
if (this.heartbeatTimer) {
|
|
237
|
+
clearInterval(this.heartbeatTimer);
|
|
238
|
+
this.heartbeatTimer = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Parse a port scan specification string into an ordered array of ports.
|
|
244
|
+
* Supports comma-separated values and dash ranges:
|
|
245
|
+
* "18601" → [18601]
|
|
246
|
+
* "18601,49620" → [18601, 49620]
|
|
247
|
+
* "49620-49629" → [49620, 49621, ..., 49629]
|
|
248
|
+
* "18601,49620-49629" → [18601, 49620, 49621, ..., 49629]
|
|
249
|
+
*/
|
|
250
|
+
export function parsePortScanSpec(spec) {
|
|
251
|
+
const ports = [];
|
|
252
|
+
const parts = spec.split(',');
|
|
253
|
+
for (const part of parts) {
|
|
254
|
+
const trimmed = part.trim();
|
|
255
|
+
if (!trimmed)
|
|
256
|
+
continue;
|
|
257
|
+
const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
258
|
+
if (rangeMatch && rangeMatch[1] !== undefined && rangeMatch[2] !== undefined) {
|
|
259
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
260
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
261
|
+
if (start > 0 && end > 0 && start <= end && end <= 65535) {
|
|
262
|
+
for (let p = start; p <= end; p++) {
|
|
263
|
+
ports.push(p);
|
|
214
264
|
}
|
|
215
265
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const port = parseInt(trimmed, 10);
|
|
269
|
+
if (port > 0 && port <= 65535) {
|
|
270
|
+
ports.push(port);
|
|
220
271
|
}
|
|
221
272
|
}
|
|
222
|
-
return [...new Set(ports)].sort((a, b) => a - b);
|
|
223
273
|
}
|
|
274
|
+
return ports;
|
|
224
275
|
}
|
|
225
276
|
//# sourceMappingURL=manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/bridge/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/bridge/manager.ts"],"names":[],"mappings":"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"}
|