{"version":3,"file":"server.cjs","names":["#eventTarget","#port","#host","#server","#wssServer","#externalServer","#debug","#clients","#sseClients","#dispatcher","#connectFunction","#externalRequestHandler","#externalUpgradeHandler"],"sources":["../../../src/server/server.ts"],"sourcesContent":["import http from 'node:http'\nimport { WebSocket, WebSocketServer } from 'ws'\nimport { parseWithBigInt, stringifyWithBigInt } from '../utils/json'\nimport type { Duplex } from 'node:stream'\n\n// Shared types\nexport interface TanStackDevtoolsEvent<\n  TEventName extends string,\n  TPayload = any,\n> {\n  type: TEventName\n  payload: TPayload\n  pluginId?: string // Optional pluginId to filter events by plugin\n}\n// Used so no new server starts up when HMR happens\ndeclare global {\n  var __TANSTACK_DEVTOOLS_SERVER__: http.Server | null\n\n  var __TANSTACK_DEVTOOLS_WSS_SERVER__: WebSocketServer | null\n\n  var __TANSTACK_EVENT_TARGET__: EventTarget | null\n}\n\n/**\n * A minimal server interface that both `http.Server` and `http2.Http2SecureServer` satisfy.\n * Used so the event bus can piggyback on any compatible server without depending on http2 types directly.\n */\nexport interface HttpServerLike {\n  on: (event: string, listener: (...args: Array<any>) => void) => this\n  removeListener: (\n    event: string,\n    listener: (...args: Array<any>) => void,\n  ) => this\n  address: () => ReturnType<http.Server['address']>\n}\n\nexport interface ServerEventBusConfig {\n  port?: number | undefined\n  host?: string | undefined\n  debug?: boolean | undefined\n  /**\n   * An external HTTP server to attach to instead of creating a standalone one.\n   * When provided, the event bus will add its SSE/POST/WS handlers to this server\n   * instead of creating and listening on its own server.\n   * Useful for piggybacking on Vite's HTTPS-enabled server.\n   */\n  httpServer?: HttpServerLike | undefined\n}\n\nexport class ServerEventBus {\n  #eventTarget: EventTarget\n  #clients = new Set<WebSocket>()\n  #sseClients = new Set<http.ServerResponse>()\n  #server: http.Server | null = null\n  #wssServer: WebSocketServer | null = null\n  #port: number\n  #host: string\n  #debug: boolean\n  #externalServer: HttpServerLike | null = null\n  #externalRequestHandler:\n    | ((req: http.IncomingMessage, res: http.ServerResponse) => void)\n    | null = null\n  #externalUpgradeHandler:\n    | ((req: http.IncomingMessage, socket: Duplex, head: Buffer) => void)\n    | null = null\n  #dispatcher = (e: Event) => {\n    const event = (e as CustomEvent).detail\n    this.debugLog('Dispatching event from dispatcher, forwarding', event)\n    this.emit(event)\n  }\n  #connectFunction = () => {\n    this.debugLog(\n      'Connection request made to event-bus, replying back with success',\n    )\n    this.#eventTarget.dispatchEvent(new CustomEvent('tanstack-connect-success'))\n  }\n  constructor({\n    port = 4206,\n    host = 'localhost',\n    debug = false,\n    httpServer,\n  }: ServerEventBusConfig = {}) {\n    this.#port = port\n    this.#host = host\n    this.#eventTarget =\n      globalThis.__TANSTACK_EVENT_TARGET__ ?? new EventTarget()\n    // we want to set the global event target only once so that we can emit/listen to events on the server\n    if (!globalThis.__TANSTACK_EVENT_TARGET__) {\n      globalThis.__TANSTACK_EVENT_TARGET__ = this.#eventTarget\n    }\n    this.#server = globalThis.__TANSTACK_DEVTOOLS_SERVER__ ?? null\n    this.#wssServer = globalThis.__TANSTACK_DEVTOOLS_WSS_SERVER__ ?? null\n    this.#externalServer = httpServer ?? null\n    this.#debug = debug\n    this.debugLog('Initializing server event bus')\n  }\n\n  private debugLog(...args: Array<any>) {\n    if (this.#debug) {\n      console.log('🌴 [tanstack-devtools:server-bus] ', ...args)\n    }\n  }\n\n  private emitToServer(event: TanStackDevtoolsEvent<string>) {\n    this.debugLog('Emitting event to specific server listeners', event)\n    this.#eventTarget.dispatchEvent(\n      new CustomEvent(event.type, { detail: event }),\n    )\n    this.debugLog('Emitting event to global server listeners', event)\n    this.#eventTarget.dispatchEvent(\n      new CustomEvent('tanstack-devtools-global', { detail: event }),\n    )\n  }\n\n  private emitEventToClients(event: TanStackDevtoolsEvent<string>) {\n    this.debugLog('Emitting event to clients', event)\n    const json = stringifyWithBigInt(event)\n\n    for (const client of this.#clients) {\n      if (client.readyState === WebSocket.OPEN) {\n        client.send(json)\n      }\n    }\n    for (const res of this.#sseClients) {\n      res.write(`data: ${json}\\n\\n`)\n    }\n  }\n\n  private emit(event: TanStackDevtoolsEvent<string>) {\n    this.emitEventToClients(event)\n    this.emitToServer(event)\n  }\n\n  private createSSEServer() {\n    if (this.#server) {\n      return this.#server\n    }\n    const server = http.createServer((req, res) => {\n      if (req.url === '/__devtools/sse') {\n        res.writeHead(200, {\n          'Content-Type': 'text/event-stream',\n          'Cache-Control': 'no-cache',\n          Connection: 'keep-alive',\n          'Access-Control-Allow-Origin': '*',\n        })\n        res.write('\\n')\n        this.debugLog('New SSE client connected')\n        this.#sseClients.add(res)\n        req.on('close', () => this.#sseClients.delete(res))\n        return\n      }\n\n      if (req.url === '/__devtools/send' && req.method === 'POST') {\n        let body = ''\n        req.on('data', (chunk) => (body += chunk))\n        req.on('end', () => {\n          try {\n            const msg = parseWithBigInt(body)\n            this.debugLog('Received event from client', msg)\n            this.emitToServer(msg)\n          } catch {}\n        })\n        res.writeHead(200).end()\n        return\n      }\n\n      res.statusCode = 404\n      res.end()\n    })\n    globalThis.__TANSTACK_DEVTOOLS_SERVER__ = server\n    this.#server = server\n    return server\n  }\n\n  private createWebSocketServer() {\n    if (this.#wssServer) {\n      return this.#wssServer\n    }\n\n    const wss = new WebSocketServer({ noServer: true })\n    this.#wssServer = wss\n    globalThis.__TANSTACK_DEVTOOLS_WSS_SERVER__ = wss\n    return wss\n  }\n\n  private handleNewConnection(wss: WebSocketServer) {\n    wss.on('connection', (ws: WebSocket) => {\n      this.debugLog('New WebSocket client connected')\n      this.#clients.add(ws)\n      ws.on('close', () => {\n        this.debugLog('WebSocket client disconnected')\n        this.#clients.delete(ws)\n      })\n      ws.on('message', (msg) => {\n        this.debugLog('Received message from WebSocket client', msg.toString())\n        const data = parseWithBigInt(msg.toString())\n        this.emitToServer(data)\n      })\n    })\n  }\n\n  start(): Promise<number> {\n    return new Promise((resolve) => {\n      if (process.env.NODE_ENV !== 'development') {\n        resolve(this.#port)\n        return\n      }\n      if (this.#server || this.#wssServer) {\n        // console.warn('Server is already running')\n        resolve(this.#port)\n        return\n      }\n\n      this.#eventTarget.addEventListener(\n        'tanstack-dispatch-event',\n        this.#dispatcher,\n      )\n      this.#eventTarget.addEventListener(\n        'tanstack-connect',\n        this.#connectFunction,\n      )\n\n      // When an external server is provided (e.g. Vite's HTTPS server),\n      // piggyback on it instead of creating a standalone server.\n      if (this.#externalServer) {\n        this.debugLog('Piggybacking on external HTTP server')\n        const wss = this.createWebSocketServer()\n        this.handleNewConnection(wss)\n\n        // Add request handler for SSE and POST endpoints\n        this.#externalRequestHandler = (\n          req: http.IncomingMessage,\n          res: http.ServerResponse,\n        ) => {\n          if (req.url === '/__devtools/sse') {\n            res.writeHead(200, {\n              'Content-Type': 'text/event-stream',\n              'Cache-Control': 'no-cache',\n              Connection: 'keep-alive',\n              'Access-Control-Allow-Origin': '*',\n            })\n            res.write('\\n')\n            this.debugLog('New SSE client connected (external server)')\n            this.#sseClients.add(res)\n            req.on('close', () => this.#sseClients.delete(res))\n            return\n          }\n\n          if (req.url === '/__devtools/send' && req.method === 'POST') {\n            let body = ''\n            req.on('data', (chunk) => (body += chunk))\n            req.on('end', () => {\n              try {\n                const msg = parseWithBigInt(body)\n                this.debugLog(\n                  'Received event from client (external server)',\n                  msg,\n                )\n                this.emitToServer(msg)\n              } catch {}\n            })\n            res.writeHead(200).end()\n            return\n          }\n        }\n\n        // Add upgrade handler for WebSocket\n        this.#externalUpgradeHandler = (\n          req: http.IncomingMessage,\n          socket: Duplex,\n          head: Buffer,\n        ) => {\n          if (req.url === '/__devtools/ws') {\n            wss.handleUpgrade(req, socket, head, (ws) => {\n              this.debugLog(\n                'WebSocket connection established (external server)',\n              )\n              wss.emit('connection', ws, req)\n            })\n          }\n        }\n\n        this.#externalServer.on('request', this.#externalRequestHandler)\n        this.#externalServer.on('upgrade', this.#externalUpgradeHandler)\n\n        // Resolve port from the external server's address\n        const address = this.#externalServer.address()\n        if (typeof address === 'object' && address) {\n          this.#port = address.port\n        }\n        this.debugLog(`Attached to external server on port ${this.#port}`)\n        resolve(this.#port)\n        return\n      }\n\n      // Standalone mode: create our own HTTP + WebSocket server\n      this.debugLog('Starting server event bus')\n      const server = this.createSSEServer()\n      const wss = this.createWebSocketServer()\n\n      this.handleNewConnection(wss)\n\n      // Handle connection upgrade for WebSocket\n      server.on('upgrade', (req, socket, head) => {\n        if (req.url === '/__devtools/ws') {\n          wss.handleUpgrade(req, socket, head, (ws) => {\n            this.debugLog('WebSocket connection established')\n            wss.emit('connection', ws, req)\n          })\n        }\n      })\n\n      const tryListen = (port: number) => {\n        server.listen(port, this.#host, () => {\n          const address = server.address()\n          if (typeof address === 'object' && address) {\n            this.#port = address.port\n          }\n          this.debugLog(`Listening on http://${this.#host}:${this.#port}`)\n          resolve(this.#port)\n        })\n      }\n\n      // Handle EADDRINUSE by retrying with port 0 (OS-assigned)\n      server.on('error', (err: NodeJS.ErrnoException) => {\n        if (err.code === 'EADDRINUSE') {\n          this.debugLog(\n            `Port ${this.#port} is in use, trying OS-assigned port...`,\n          )\n          // Use port 0 to let OS assign an available port\n          tryListen(0)\n        }\n      })\n\n      tryListen(this.#port)\n    })\n  }\n\n  /**\n   * Get the port the server is listening on.\n   * This may differ from the configured port if the original port was in use.\n   */\n  get port(): number {\n    return this.#port\n  }\n\n  stop() {\n    // Only close the server if we own it (standalone mode)\n    if (!this.#externalServer) {\n      this.#server?.close(() => {\n        this.debugLog('Server stopped')\n      })\n    } else {\n      // Remove our listeners from the external server without closing it\n      if (this.#externalRequestHandler) {\n        this.#externalServer.removeListener(\n          'request',\n          this.#externalRequestHandler,\n        )\n        this.#externalRequestHandler = null\n      }\n      if (this.#externalUpgradeHandler) {\n        this.#externalServer.removeListener(\n          'upgrade',\n          this.#externalUpgradeHandler,\n        )\n        this.#externalUpgradeHandler = null\n      }\n      this.debugLog('Detached from external server')\n    }\n    this.#wssServer?.close(() => {\n      this.debugLog('WebSocket server stopped')\n    })\n    this.debugLog('Clearing all connections')\n    this.#clients.clear()\n    this.#sseClients.forEach((res) => res.end())\n    this.#sseClients.clear()\n    this.debugLog('Cleared all WS/SSE connections')\n    this.#server = null\n    this.#wssServer = null\n    this.#externalServer = null\n    this.#eventTarget.removeEventListener(\n      'tanstack-dispatch-event',\n      this.#dispatcher,\n    )\n    this.#eventTarget.removeEventListener(\n      'tanstack-connect',\n      this.#connectFunction,\n    )\n    this.debugLog('[tanstack-devtools] All connections cleared')\n  }\n}\n"],"mappings":"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"}