@xstate-devtools/adapter 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.ts +41 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate-devtools/adapter",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
package/src/server.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Server entrypoint — exposes a WebSocket bridge so the DevTools panel
2
2
  // can connect to actors running in Node.
3
+ import { createServer as createTcpServer } from 'node:net'
3
4
  import type {
4
5
  ExtensionToPageMessage,
5
6
  PageToExtensionMessage,
@@ -12,6 +13,28 @@ import {
12
13
  } from './logging.js'
13
14
  import { sanitize } from './sanitize.js'
14
15
 
16
+ /**
17
+ * Find the lowest TCP port >= `start` that is not currently in use.
18
+ * Uses a temporary TCP server to probe; the port is released before resolving.
19
+ */
20
+ function getAvailablePort(start: number): Promise<number> {
21
+ return new Promise((resolve, reject) => {
22
+ const probe = createTcpServer()
23
+ probe.unref()
24
+ probe.on('error', (err: NodeJS.ErrnoException) => {
25
+ if (err.code === 'EADDRINUSE') {
26
+ resolve(getAvailablePort(start + 1))
27
+ } else {
28
+ reject(err)
29
+ }
30
+ })
31
+ probe.listen(start, '127.0.0.1', () => {
32
+ const port = (probe.address() as { port: number }).port
33
+ probe.close(() => resolve(port))
34
+ })
35
+ })
36
+ }
37
+
15
38
  export interface ServerAdapterOptions {
16
39
  /** Port to listen on. Defaults to env XSTATE_DEVTOOLS_PORT or 9301. */
17
40
  port?: number
@@ -89,6 +112,8 @@ interface CachedServer {
89
112
  buffer: string[]
90
113
  bufferSize: number
91
114
  activated: boolean
115
+ /** Resolves with the actual TCP port once the WS server is listening. */
116
+ port: Promise<number>
92
117
  close: () => void
93
118
  }
94
119
 
@@ -132,12 +157,20 @@ export function createServerAdapter(options: ServerAdapterOptions = {}) {
132
157
  let wss: any = null
133
158
  let closed = false
134
159
 
160
+ let portResolve!: (port: number) => void
161
+ let portReject!: (err: unknown) => void
162
+ const portPromise = new Promise<number>((res, rej) => {
163
+ portResolve = res
164
+ portReject = rej
165
+ })
166
+
135
167
  server = {
136
168
  clients,
137
169
  dispatchHandlers,
138
170
  buffer,
139
171
  bufferSize,
140
172
  activated: false,
173
+ port: portPromise,
141
174
  close: () => {
142
175
  closed = true
143
176
  infoLog('closing WebSocket server', { host, port, clientCount: clients.size })
@@ -160,8 +193,12 @@ export function createServerAdapter(options: ServerAdapterOptions = {}) {
160
193
  const mod = await import('ws')
161
194
  const WSServer = (mod as any).WebSocketServer ?? (mod as any).Server
162
195
  if (closed) return
163
- wss = new WSServer({ port, host })
164
- infoLog('WebSocket server listening', { host, port })
196
+ const actualPort = await getAvailablePort(port)
197
+ if (closed) return
198
+ wss = new WSServer({ port: actualPort, host })
199
+ process.env.XSTATE_DEVTOOLS_PORT = String(actualPort)
200
+ portResolve(actualPort)
201
+ infoLog('WebSocket server listening', { host, port: actualPort })
165
202
  wss.on('connection', (ws: ClientLike) => {
166
203
  infoLog('panel connected to WebSocket server', {
167
204
  host,
@@ -214,6 +251,7 @@ export function createServerAdapter(options: ServerAdapterOptions = {}) {
214
251
  warnLog('WS server error', { host, port, message: err.message })
215
252
  })
216
253
  } catch (e) {
254
+ portReject(e)
217
255
  warnLog('could not start server adapter — install `ws` to enable', {
218
256
  host,
219
257
  port,
@@ -268,5 +306,5 @@ export function createServerAdapter(options: ServerAdapterOptions = {}) {
268
306
  }
269
307
 
270
308
  const inspector = createInspector(transport, 'srv')
271
- return { ...inspector, close: server.close }
309
+ return { ...inspector, close: server.close, port: server.port }
272
310
  }