@mandujs/core 0.20.1 → 0.20.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.20.1",
3
+ "version": "0.20.3",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -559,7 +559,7 @@ export function generateHMRClientScript(port: number): string {
559
559
 
560
560
  function connect() {
561
561
  try {
562
- ws = new WebSocket('ws://localhost:' + HMR_PORT);
562
+ ws = new WebSocket('ws://' + window.location.hostname + ':' + HMR_PORT);
563
563
 
564
564
  ws.onopen = function() {
565
565
  console.log('[Mandu HMR] Connected');
@@ -84,13 +84,29 @@ export type MCPConnectionStatus =
84
84
  // Constants
85
85
  // ============================================================================
86
86
 
87
+ /**
88
+ * #176: 런타임에 현재 페이지 호스트/포트에서 MCP WebSocket URL을 유추한다.
89
+ * Bun 번들러가 `typeof window`를 컴파일 시 dead-code 제거하므로
90
+ * 함수로 감싸서 런타임 평가를 보장한다.
91
+ */
92
+ function resolveDefaultServerUrl(): string {
93
+ try {
94
+ // eslint-disable-next-line no-restricted-globals
95
+ if (globalThis.document && globalThis.location) {
96
+ return `ws://${globalThis.location.hostname}:${globalThis.location.port}/mcp`;
97
+ }
98
+ } catch { /* SSR / Node / Bun 환경 */ }
99
+ return 'ws://localhost:3333/mcp';
100
+ }
101
+
87
102
  const DEFAULT_OPTIONS: Required<MCPConnectorOptions> = {
88
- serverUrl: 'ws://localhost:3333/mcp',
103
+ serverUrl: '', // connect() 시 resolveDefaultServerUrl()로 대체
89
104
  connectionTimeout: 5000,
90
105
  requestTimeout: 30000,
91
- autoReconnect: true,
92
- reconnectInterval: 3000,
93
- maxReconnectAttempts: 5,
106
+ // #175: MCP 서버가 없을 수 있으므로 기본 비활성화
107
+ autoReconnect: false,
108
+ reconnectInterval: 1000,
109
+ maxReconnectAttempts: 3,
94
110
  };
95
111
 
96
112
  // ============================================================================
@@ -142,7 +158,9 @@ export class MCPConnector {
142
158
  }, this.options.connectionTimeout);
143
159
 
144
160
  try {
145
- this.ws = new WebSocket(this.options.serverUrl);
161
+ // #176: serverUrl이면 런타임에서 현재 페이지 기준으로 유추
162
+ const url = this.options.serverUrl || resolveDefaultServerUrl();
163
+ this.ws = new WebSocket(url);
146
164
 
147
165
  this.ws.onopen = () => {
148
166
  clearTimeout(timeout);
@@ -377,10 +395,11 @@ export class MCPConnector {
377
395
  private handleDisconnect(): void {
378
396
  this.ws = null;
379
397
 
380
- if (this.options.autoReconnect && this.status !== 'disconnected') {
398
+ // #175: handleConnectionError에서 이미 reconnect 예약한 경우 중복 방지
399
+ if (this.options.autoReconnect && this.status !== 'disconnected' && !this.reconnectTimer) {
381
400
  this.setStatus('error');
382
401
  this.scheduleReconnect();
383
- } else {
402
+ } else if (!this.reconnectTimer) {
384
403
  this.setStatus('disconnected');
385
404
  }
386
405
  }
@@ -389,12 +408,18 @@ export class MCPConnector {
389
408
  console.warn('[Mandu Kitchen] MCP connection error:', error.message);
390
409
  this.setStatus('error');
391
410
 
392
- if (this.options.autoReconnect) {
411
+ if (this.options.autoReconnect && !this.reconnectTimer) {
393
412
  this.scheduleReconnect();
394
413
  }
395
414
  }
396
415
 
397
416
  private scheduleReconnect(): void {
417
+ // #175: 기존 타이머 취소 (중복 방지)
418
+ if (this.reconnectTimer) {
419
+ clearTimeout(this.reconnectTimer);
420
+ this.reconnectTimer = null;
421
+ }
422
+
398
423
  if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
399
424
  console.warn('[Mandu Kitchen] Max reconnection attempts reached');
400
425
  this.setStatus('disconnected');
@@ -402,15 +427,21 @@ export class MCPConnector {
402
427
  }
403
428
 
404
429
  this.reconnectAttempts++;
430
+ // #175: exponential backoff (1s → 2s → 4s → ... → max 30s)
431
+ const delay = Math.min(
432
+ this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
433
+ 30_000,
434
+ );
405
435
  console.log(
406
- `[Mandu Kitchen] Reconnecting in ${this.options.reconnectInterval}ms (attempt ${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`
436
+ `[Mandu Kitchen] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`
407
437
  );
408
438
 
409
439
  this.reconnectTimer = setTimeout(() => {
440
+ this.reconnectTimer = null;
410
441
  this.connect().catch(() => {
411
442
  // 에러는 handleConnectionError에서 처리됨
412
443
  });
413
- }, this.options.reconnectInterval);
444
+ }, delay);
414
445
  }
415
446
 
416
447
  private async sendRequest<T>(method: string, params: unknown): Promise<T> {
@@ -372,7 +372,7 @@ window.__MANDU_HMR_PORT__ = ${hmrPort};
372
372
 
373
373
  function connect() {
374
374
  try {
375
- ws = new WebSocket('ws://localhost:${hmrPort}');
375
+ ws = new WebSocket('ws://' + window.location.hostname + ':${hmrPort}');
376
376
  ws.onopen = function() {
377
377
  console.log('[Mandu HMR] Connected');
378
378
  reconnectAttempts = 0;
@@ -618,7 +618,7 @@ window.__MANDU_HMR_PORT__ = ${hmrPort};
618
618
 
619
619
  function connect() {
620
620
  try {
621
- ws = new WebSocket('ws://localhost:${hmrPort}');
621
+ ws = new WebSocket('ws://' + window.location.hostname + ':${hmrPort}');
622
622
  ws.onopen = function() {
623
623
  console.log('[Mandu HMR] Connected');
624
624
  reconnectAttempts = 0;