@0x1f320.sh/why-did-you-render-mcp 1.0.0-dev.2 → 1.0.0-dev.4

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.
@@ -50,21 +50,27 @@ function patchDevToolsHook(onCommit) {
50
50
  function buildOptions(opts) {
51
51
  const wsUrl = opts?.wsUrl ?? DEFAULT_WS_URL;
52
52
  const projectId = opts?.projectId ?? globalThis.location?.origin ?? "default";
53
+ const MAX_QUEUE_SIZE = 1e3;
54
+ const BASE_DELAY = 1e3;
55
+ const MAX_DELAY = 3e4;
53
56
  let ws = null;
54
57
  let queue = [];
55
58
  let commitId = 0;
59
+ let retryDelay = BASE_DELAY;
56
60
  patchDevToolsHook(() => {
57
61
  commitId++;
58
62
  });
59
63
  function connect() {
60
64
  ws = new WebSocket(wsUrl);
61
65
  ws.addEventListener("open", () => {
66
+ retryDelay = BASE_DELAY;
62
67
  for (const msg of queue) ws?.send(JSON.stringify(msg));
63
68
  queue = [];
64
69
  });
65
70
  ws.addEventListener("close", () => {
66
71
  ws = null;
67
- setTimeout(connect, 1e3);
72
+ setTimeout(connect, retryDelay);
73
+ retryDelay = Math.min(retryDelay * 2, MAX_DELAY);
68
74
  });
69
75
  ws.addEventListener("error", () => {
70
76
  ws?.close();
@@ -73,19 +79,42 @@ function buildOptions(opts) {
73
79
  connect();
74
80
  function send(msg) {
75
81
  if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(msg));
76
- else queue.push(msg);
82
+ else {
83
+ if (queue.length >= MAX_QUEUE_SIZE) queue.shift();
84
+ queue.push(msg);
85
+ }
77
86
  }
78
- return { notifier(info) {
87
+ let pendingBatch = null;
88
+ let flushScheduled = false;
89
+ function flushBatch() {
90
+ flushScheduled = false;
91
+ if (!pendingBatch || pendingBatch.reports.length === 0) return;
79
92
  send({
80
- type: "render",
93
+ type: "render-batch",
81
94
  projectId,
82
- commitId,
83
- payload: {
84
- displayName: info.displayName,
85
- reason: sanitizeReason(info.reason),
86
- hookName: info.hookName
87
- }
95
+ commitId: pendingBatch.commitId,
96
+ payload: pendingBatch.reports
88
97
  });
98
+ pendingBatch = null;
99
+ }
100
+ return { notifier(info) {
101
+ const report = {
102
+ displayName: info.displayName,
103
+ reason: sanitizeReason(info.reason),
104
+ hookName: info.hookName
105
+ };
106
+ if (pendingBatch && pendingBatch.commitId === commitId) pendingBatch.reports.push(report);
107
+ else {
108
+ if (pendingBatch) flushBatch();
109
+ pendingBatch = {
110
+ commitId,
111
+ reports: [report]
112
+ };
113
+ }
114
+ if (!flushScheduled) {
115
+ flushScheduled = true;
116
+ queueMicrotask(flushBatch);
117
+ }
89
118
  } };
90
119
  }
91
120
  //#endregion
@@ -49,21 +49,27 @@ function patchDevToolsHook(onCommit) {
49
49
  function buildOptions(opts) {
50
50
  const wsUrl = opts?.wsUrl ?? DEFAULT_WS_URL;
51
51
  const projectId = opts?.projectId ?? globalThis.location?.origin ?? "default";
52
+ const MAX_QUEUE_SIZE = 1e3;
53
+ const BASE_DELAY = 1e3;
54
+ const MAX_DELAY = 3e4;
52
55
  let ws = null;
53
56
  let queue = [];
54
57
  let commitId = 0;
58
+ let retryDelay = BASE_DELAY;
55
59
  patchDevToolsHook(() => {
56
60
  commitId++;
57
61
  });
58
62
  function connect() {
59
63
  ws = new WebSocket(wsUrl);
60
64
  ws.addEventListener("open", () => {
65
+ retryDelay = BASE_DELAY;
61
66
  for (const msg of queue) ws?.send(JSON.stringify(msg));
62
67
  queue = [];
63
68
  });
64
69
  ws.addEventListener("close", () => {
65
70
  ws = null;
66
- setTimeout(connect, 1e3);
71
+ setTimeout(connect, retryDelay);
72
+ retryDelay = Math.min(retryDelay * 2, MAX_DELAY);
67
73
  });
68
74
  ws.addEventListener("error", () => {
69
75
  ws?.close();
@@ -72,19 +78,42 @@ function buildOptions(opts) {
72
78
  connect();
73
79
  function send(msg) {
74
80
  if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(msg));
75
- else queue.push(msg);
81
+ else {
82
+ if (queue.length >= MAX_QUEUE_SIZE) queue.shift();
83
+ queue.push(msg);
84
+ }
76
85
  }
77
- return { notifier(info) {
86
+ let pendingBatch = null;
87
+ let flushScheduled = false;
88
+ function flushBatch() {
89
+ flushScheduled = false;
90
+ if (!pendingBatch || pendingBatch.reports.length === 0) return;
78
91
  send({
79
- type: "render",
92
+ type: "render-batch",
80
93
  projectId,
81
- commitId,
82
- payload: {
83
- displayName: info.displayName,
84
- reason: sanitizeReason(info.reason),
85
- hookName: info.hookName
86
- }
94
+ commitId: pendingBatch.commitId,
95
+ payload: pendingBatch.reports
87
96
  });
97
+ pendingBatch = null;
98
+ }
99
+ return { notifier(info) {
100
+ const report = {
101
+ displayName: info.displayName,
102
+ reason: sanitizeReason(info.reason),
103
+ hookName: info.hookName
104
+ };
105
+ if (pendingBatch && pendingBatch.commitId === commitId) pendingBatch.reports.push(report);
106
+ else {
107
+ if (pendingBatch) flushBatch();
108
+ pendingBatch = {
109
+ commitId,
110
+ reports: [report]
111
+ };
112
+ }
113
+ if (!flushScheduled) {
114
+ flushScheduled = true;
115
+ queueMicrotask(flushBatch);
116
+ }
88
117
  } };
89
118
  }
90
119
  //#endregion
@@ -344,11 +344,10 @@ function createWsServer(port) {
344
344
  ws.on("message", (raw) => {
345
345
  try {
346
346
  const msg = JSON.parse(String(raw));
347
- if (msg.type === "render") {
348
- const projectId = msg.projectId ?? "default";
349
- heartbeat.setProjectId(ws, projectId);
350
- store.addRender(msg.payload, projectId, msg.commitId);
351
- }
347
+ const projectId = msg.projectId ?? "default";
348
+ heartbeat.setProjectId(ws, projectId);
349
+ if (msg.type === "render") store.addRender(msg.payload, projectId, msg.commitId);
350
+ else if (msg.type === "render-batch") for (const report of msg.payload) store.addRender(report, projectId, msg.commitId);
352
351
  } catch {
353
352
  console.error("[wdyr-mcp] invalid message received");
354
353
  }
@@ -371,10 +370,22 @@ const server = new McpServer({
371
370
  });
372
371
  registerTools(server);
373
372
  async function main() {
374
- createWsServer(Number(process.env.WDYR_WS_PORT) || DEFAULT_WS_PORT);
373
+ const wss = createWsServer(Number(process.env.WDYR_WS_PORT) || DEFAULT_WS_PORT);
375
374
  const transport = new StdioServerTransport();
376
375
  await server.connect(transport);
377
376
  console.error("[wdyr-mcp] MCP server running on stdio");
377
+ let shuttingDown = false;
378
+ async function shutdown() {
379
+ if (shuttingDown) return;
380
+ shuttingDown = true;
381
+ console.error("[wdyr-mcp] Shutting down…");
382
+ wss?.close();
383
+ await server.close();
384
+ process.exit(0);
385
+ }
386
+ process.stdin.on("end", shutdown);
387
+ process.on("SIGTERM", shutdown);
388
+ process.on("SIGINT", shutdown);
378
389
  }
379
390
  main().catch((error) => {
380
391
  console.error("[wdyr-mcp] Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0x1f320.sh/why-did-you-render-mcp",
3
- "version": "1.0.0-dev.2",
3
+ "version": "1.0.0-dev.4",
4
4
  "type": "module",
5
5
  "description": "MCP server that collects why-did-you-render data from browser and exposes it to coding agents",
6
6
  "license": "MIT",