vite-plugin-io1 0.1.1 → 0.1.2

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.
@@ -3,6 +3,7 @@
3
3
  * contract with the CLI - which flags it takes, and how it announces a live host - is unit-tested
4
4
  * in isolation and lives in one place.
5
5
  */
6
+ import type { AddressInfo } from 'node:net';
6
7
  export interface RelayInvocation {
7
8
  /** Hosts to claim, one `--host` each (the CLI appends across repeats). At least one. */
8
9
  readonly hosts: readonly string[];
@@ -35,4 +36,14 @@ export declare function buildRelayArgs(invocation: RelayInvocation): string[];
35
36
  * URLs but must not be mistaken for a live host.
36
37
  */
37
38
  export declare function parseLiveUrl(line: string): string | null;
39
+ /**
40
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
41
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
42
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
43
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
44
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
45
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
46
+ * whatever unrelated server already holds that IPv4 port.
47
+ */
48
+ export declare function relayTargetUrl(address: AddressInfo): string;
38
49
  //# sourceMappingURL=announce.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,eAAe,GAAG,MAAM,EAAE,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGxD"}
1
+ {"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,eAAe,GAAG,MAAM,EAAE,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAK3D"}
package/dist/announce.js CHANGED
@@ -39,4 +39,19 @@ export function parseLiveUrl(line) {
39
39
  const match = /(https?:\/\/\S+)\s+live\s*$/u.exec(line);
40
40
  return match ? match[1] : null;
41
41
  }
42
+ /**
43
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
44
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
45
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
46
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
47
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
48
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
49
+ * whatever unrelated server already holds that IPv4 port.
50
+ */
51
+ export function relayTargetUrl(address) {
52
+ const wildcard = '' === address.address || '0.0.0.0' === address.address || '::' === address.address;
53
+ const host = wildcard ? ('IPv6' === address.family ? '::1' : '127.0.0.1') : address.address;
54
+ const authority = host.includes(':') ? `[${host}]` : host;
55
+ return `http://${authority}:${address.port}`;
56
+ }
42
57
  //# sourceMappingURL=announce.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,IAAI,EAAE,CAAA;IAC7C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IAC/E,IAAI,UAAU,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,IAAI,EAAE,CAAA;IAC7C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IAC/E,IAAI,UAAU,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoB;IACjD,MAAM,QAAQ,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC,OAAO,CAAA;IACpG,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;IAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IACzD,OAAO,UAAU,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,CAAA;AAC9C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -27,11 +27,12 @@ export interface Io1PluginOptions {
27
27
  }
28
28
  /**
29
29
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
30
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
31
- * tied to the server - spawned once it is listening, killed when it closes - so a shared URL never
32
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
33
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
34
- * (bad auth, host held by another account) and is surfaced, not retried.
30
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
31
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
32
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
33
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
34
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
35
+ * account) and is surfaced, not retried.
35
36
  */
36
37
  export declare function io1(options?: Io1PluginOptions): Plugin;
37
38
  export default io1;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,MAAM,CAAA;AAIhE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjE,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAUD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA0F1D;AAyBD,eAAe,GAAG,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,MAAM,CAAA;AAIhE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjE,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAyG1D;AAyBD,eAAe,GAAG,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { buildRelayArgs, parseLiveUrl } from './announce.js';
2
+ import { buildRelayArgs, parseLiveUrl, relayTargetUrl } from './announce.js';
3
3
  import { resolveBitmot } from './binary.js';
4
4
  function normalizeHosts(host) {
5
5
  const raw = host ?? process.env.IO1_DEV_HOST;
@@ -9,11 +9,12 @@ function normalizeHosts(host) {
9
9
  }
10
10
  /**
11
11
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
12
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
13
- * tied to the server - spawned once it is listening, killed when it closes - so a shared URL never
14
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
15
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
16
- * (bad auth, host held by another account) and is surfaced, not retried.
12
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
13
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
14
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
15
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
16
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
17
+ * account) and is surfaced, not retried.
17
18
  */
18
19
  export function io1(options = {}) {
19
20
  const hosts = normalizeHosts(options.host);
@@ -39,10 +40,24 @@ export function io1(options = {}) {
39
40
  child = undefined;
40
41
  process.removeListener('exit', onExit); // avoid piling up a listener per dev-server restart
41
42
  };
42
- httpServer.once('listening', () => {
43
- void start(server).catch((error) => {
44
- logger.error(`[io1] ${error.message}`);
45
- });
43
+ // Arm the relay by intercepting listen() rather than only awaiting the 'listening' event: Vite
44
+ // calls listen once it has resolved the final port - the configured one, or (under
45
+ // strictPort:false) the next free port after a collision - so wrapping the very call that binds
46
+ // the socket is what guarantees we read the port the server actually took. The one-time
47
+ // 'listening' hook fires the instant the bind completes; `armed` keeps a second listen() from
48
+ // spawning a second relay. The casts are the price of replacing an overloaded native method.
49
+ const originalListen = httpServer.listen.bind(httpServer);
50
+ let armed = false;
51
+ httpServer.listen = ((...args) => {
52
+ if (!armed) {
53
+ armed = true;
54
+ httpServer.once('listening', () => {
55
+ void start(server).catch((error) => {
56
+ logger.error(`[io1] ${error.message}`);
57
+ });
58
+ });
59
+ }
60
+ return originalListen(...args);
46
61
  });
47
62
  httpServer.once('close', onExit);
48
63
  process.once('exit', onExit);
@@ -51,7 +66,7 @@ export function io1(options = {}) {
51
66
  const logger = server.config.logger;
52
67
  const address = server.httpServer?.address();
53
68
  if (!address || typeof address === 'string') {
54
- logger.error('[io1] could not determine the dev server port; relay not started');
69
+ logger.error('[io1] could not determine the dev server address; relay not started');
55
70
  return;
56
71
  }
57
72
  const binary = await resolveBitmot({
@@ -68,7 +83,7 @@ export function io1(options = {}) {
68
83
  account: options.account,
69
84
  gateway: options.gateway,
70
85
  extraArgs: options.extraArgs,
71
- to: `http://127.0.0.1:${address.port}`,
86
+ to: relayTargetUrl(address),
72
87
  });
73
88
  if (stopping)
74
89
  return; // the server closed while we were resolving the binary
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-io1",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Publish your Vite dev server to the public internet over a bitmot io1 relay, with the CLI auto-managed.",
5
5
  "keywords": [
6
6
  "vite",
package/src/announce.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  * in isolation and lives in one place.
5
5
  */
6
6
 
7
+ import type { AddressInfo } from 'node:net'
8
+
7
9
  export interface RelayInvocation {
8
10
  /** Hosts to claim, one `--host` each (the CLI appends across repeats). At least one. */
9
11
  readonly hosts: readonly string[]
@@ -52,3 +54,19 @@ export function parseLiveUrl(line: string): string | null {
52
54
  const match = /(https?:\/\/\S+)\s+live\s*$/u.exec(line)
53
55
  return match ? match[1]! : null
54
56
  }
57
+
58
+ /**
59
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
60
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
61
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
62
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
63
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
64
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
65
+ * whatever unrelated server already holds that IPv4 port.
66
+ */
67
+ export function relayTargetUrl(address: AddressInfo): string {
68
+ const wildcard = '' === address.address || '0.0.0.0' === address.address || '::' === address.address
69
+ const host = wildcard ? ('IPv6' === address.family ? '::1' : '127.0.0.1') : address.address
70
+ const authority = host.includes(':') ? `[${host}]` : host
71
+ return `http://${authority}:${address.port}`
72
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, type ChildProcess } from 'node:child_process'
2
2
  import type { Plugin, PreviewServer, ViteDevServer } from 'vite'
3
- import { buildRelayArgs, parseLiveUrl } from './announce.js'
3
+ import { buildRelayArgs, parseLiveUrl, relayTargetUrl } from './announce.js'
4
4
  import { resolveBitmot } from './binary.js'
5
5
 
6
6
  export type { RelayInvocation } from './announce.js'
@@ -41,11 +41,12 @@ type ServeServer = ViteDevServer | PreviewServer
41
41
 
42
42
  /**
43
43
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
44
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
45
- * tied to the server - spawned once it is listening, killed when it closes - so a shared URL never
46
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
47
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
48
- * (bad auth, host held by another account) and is surfaced, not retried.
44
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
45
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
46
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
47
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
48
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
49
+ * account) and is surfaced, not retried.
49
50
  */
50
51
  export function io1(options: Io1PluginOptions = {}): Plugin {
51
52
  const hosts = normalizeHosts(options.host)
@@ -74,11 +75,26 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
74
75
  process.removeListener('exit', onExit) // avoid piling up a listener per dev-server restart
75
76
  }
76
77
 
77
- httpServer.once('listening', () => {
78
- void start(server).catch((error: unknown) => {
79
- logger.error(`[io1] ${(error as Error).message}`)
80
- })
81
- })
78
+ // Arm the relay by intercepting listen() rather than only awaiting the 'listening' event: Vite
79
+ // calls listen once it has resolved the final port - the configured one, or (under
80
+ // strictPort:false) the next free port after a collision - so wrapping the very call that binds
81
+ // the socket is what guarantees we read the port the server actually took. The one-time
82
+ // 'listening' hook fires the instant the bind completes; `armed` keeps a second listen() from
83
+ // spawning a second relay. The casts are the price of replacing an overloaded native method.
84
+ const originalListen = httpServer.listen.bind(httpServer) as unknown as (...args: unknown[]) => unknown
85
+ let armed = false
86
+ httpServer.listen = ((...args: unknown[]): unknown => {
87
+ if (!armed) {
88
+ armed = true
89
+ httpServer.once('listening', () => {
90
+ void start(server).catch((error: unknown) => {
91
+ logger.error(`[io1] ${(error as Error).message}`)
92
+ })
93
+ })
94
+ }
95
+ return originalListen(...args)
96
+ }) as unknown as typeof httpServer.listen
97
+
82
98
  httpServer.once('close', onExit)
83
99
  process.once('exit', onExit)
84
100
  }
@@ -87,7 +103,7 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
87
103
  const logger = server.config.logger
88
104
  const address = server.httpServer?.address()
89
105
  if (!address || typeof address === 'string') {
90
- logger.error('[io1] could not determine the dev server port; relay not started')
106
+ logger.error('[io1] could not determine the dev server address; relay not started')
91
107
  return
92
108
  }
93
109
 
@@ -105,7 +121,7 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
105
121
  account: options.account,
106
122
  gateway: options.gateway,
107
123
  extraArgs: options.extraArgs,
108
- to: `http://127.0.0.1:${address.port}`,
124
+ to: relayTargetUrl(address),
109
125
  })
110
126
 
111
127
  if (stopping) return // the server closed while we were resolving the binary