appium-ios-remotexpc 2.3.0 → 2.4.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [2.4.0](https://github.com/appium/appium-ios-remotexpc/compare/v2.3.0...v2.4.0) (2026-06-10)
2
+
3
+ ### Features
4
+
5
+ * Only perform packet tap lazily ([#234](https://github.com/appium/appium-ios-remotexpc/issues/234)) ([d11c61d](https://github.com/appium/appium-ios-remotexpc/commit/d11c61d5cf13df3d7510a3cde91673f5b86e4871))
6
+
1
7
  ## [2.3.0](https://github.com/appium/appium-ios-remotexpc/compare/v2.2.5...v2.3.0) (2026-06-01)
2
8
 
3
9
  ### Features
@@ -1,5 +1,7 @@
1
1
  import type { PacketConsumer } from 'appium-ios-tuntap';
2
2
  import { EventEmitter } from 'node:events';
3
+ import type { PacketSource } from '../types.js';
4
+ type TunnelPacketBinding = Pick<PacketSource, 'addPacketConsumer' | 'removePacketConsumer'>;
3
5
  /**
4
6
  * Server that exposes packet streaming from a tunnel over TCP
5
7
  * This allows cross-process access to tunnel packet streams
@@ -9,18 +11,30 @@ export declare class PacketStreamServer extends EventEmitter {
9
11
  private server;
10
12
  private readonly clients;
11
13
  private packetConsumer;
14
+ private tunnel;
15
+ private attachedToTunnel;
12
16
  constructor(port: number);
17
+ /**
18
+ * Bind the tunnel whose packet consumer is attached only while TCP clients are connected.
19
+ */
20
+ bindTunnel(tunnel: TunnelPacketBinding): void;
13
21
  /**
14
22
  * Start the packet stream server
15
23
  * @throws {Error} If server is already started
16
24
  */
17
25
  start(): Promise<void>;
18
26
  stop(): Promise<void>;
27
+ /**
28
+ * Returns the packet consumer used when attached to a tunnel.
29
+ * Prefer {@link bindTunnel} for lifecycle management.
30
+ */
19
31
  getPacketConsumer(): PacketConsumer | null;
20
32
  /**
21
33
  * Handle new client connection
22
34
  */
23
35
  private handleClientConnection;
36
+ private attachToTunnel;
37
+ private detachFromTunnel;
24
38
  /**
25
39
  * Create packet consumer that broadcasts packets to all connected clients
26
40
  */
@@ -38,4 +52,5 @@ export declare class PacketStreamServer extends EventEmitter {
38
52
  */
39
53
  private createMessage;
40
54
  }
55
+ export {};
41
56
  //# sourceMappingURL=packet-stream-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"packet-stream-server.d.ts","sourceRoot":"","sources":["../../../../src/lib/tunnel/packet-stream-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAKtC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,cAAc,CAA+B;gBAExB,IAAI,EAAE,MAAM;IAIzC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,iBAAiB,IAAI,cAAc,GAAG,IAAI;IAI1C;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;OAEG;YACW,eAAe;IAwB7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,OAAO,CAAC,aAAa;CAItB"}
1
+ {"version":3,"file":"packet-stream-server.d.ts","sourceRoot":"","sources":["../../../../src/lib/tunnel/packet-stream-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,KAAK,mBAAmB,GAAG,IAAI,CAC7B,YAAY,EACZ,mBAAmB,GAAG,sBAAsB,CAC7C,CAAC;AAEF;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAOtC,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,gBAAgB,CAAS;gBAEJ,IAAI,EAAE,MAAM;IAIzC;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAO7C;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;;OAGG;IACH,iBAAiB,IAAI,cAAc,GAAG,IAAI;IAI1C;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA0B9B,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;IAUxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;OAEG;YACW,eAAe;IAwB7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,OAAO,CAAC,aAAa;CAItB"}
@@ -11,10 +11,21 @@ export class PacketStreamServer extends EventEmitter {
11
11
  server = null;
12
12
  clients = new Set();
13
13
  packetConsumer = null;
14
+ tunnel = null;
15
+ attachedToTunnel = false;
14
16
  constructor(port) {
15
17
  super();
16
18
  this.port = port;
17
19
  }
20
+ /**
21
+ * Bind the tunnel whose packet consumer is attached only while TCP clients are connected.
22
+ */
23
+ bindTunnel(tunnel) {
24
+ this.tunnel = tunnel;
25
+ if (this.clients.size > 0) {
26
+ this.attachToTunnel();
27
+ }
28
+ }
18
29
  /**
19
30
  * Start the packet stream server
20
31
  * @throws {Error} If server is already started
@@ -27,7 +38,6 @@ export class PacketStreamServer extends EventEmitter {
27
38
  this.handleClientConnection(client);
28
39
  });
29
40
  const server = this.server;
30
- this.packetConsumer = this.createPacketConsumer();
31
41
  await new Promise((resolve, reject) => {
32
42
  server.once('error', reject);
33
43
  server.listen(this.port, resolve);
@@ -35,6 +45,7 @@ export class PacketStreamServer extends EventEmitter {
35
45
  log.info(`Packet stream server listening on port ${this.port}`);
36
46
  }
37
47
  async stop() {
48
+ this.detachFromTunnel();
38
49
  for (const client of this.clients) {
39
50
  client.destroy();
40
51
  }
@@ -48,6 +59,10 @@ export class PacketStreamServer extends EventEmitter {
48
59
  this.server = null;
49
60
  }
50
61
  }
62
+ /**
63
+ * Returns the packet consumer used when attached to a tunnel.
64
+ * Prefer {@link bindTunnel} for lifecycle management.
65
+ */
51
66
  getPacketConsumer() {
52
67
  return this.packetConsumer;
53
68
  }
@@ -57,15 +72,43 @@ export class PacketStreamServer extends EventEmitter {
57
72
  handleClientConnection(client) {
58
73
  log.info(`Client connected from ${client.remoteAddress}`);
59
74
  this.clients.add(client);
75
+ if (this.clients.size === 1) {
76
+ this.attachToTunnel();
77
+ }
78
+ const onClientGone = () => {
79
+ this.clients.delete(client);
80
+ if (this.clients.size === 0) {
81
+ this.detachFromTunnel();
82
+ }
83
+ };
60
84
  client.on('close', () => {
61
85
  log.info(`Client disconnected from ${client.remoteAddress}`);
62
- this.clients.delete(client);
86
+ onClientGone();
63
87
  });
64
88
  client.on('error', (err) => {
65
89
  log.error(`Client error: ${err}`);
66
- this.clients.delete(client);
90
+ onClientGone();
67
91
  });
68
92
  }
93
+ attachToTunnel() {
94
+ if (this.attachedToTunnel || !this.tunnel) {
95
+ return;
96
+ }
97
+ if (!this.packetConsumer) {
98
+ this.packetConsumer = this.createPacketConsumer();
99
+ }
100
+ this.tunnel.addPacketConsumer(this.packetConsumer);
101
+ this.attachedToTunnel = true;
102
+ log.debug('Attached packet consumer to tunnel');
103
+ }
104
+ detachFromTunnel() {
105
+ if (!this.attachedToTunnel || !this.tunnel || !this.packetConsumer) {
106
+ return;
107
+ }
108
+ this.tunnel.removePacketConsumer(this.packetConsumer);
109
+ this.attachedToTunnel = false;
110
+ log.debug('Detached packet consumer from tunnel');
111
+ }
69
112
  /**
70
113
  * Create packet consumer that broadcasts packets to all connected clients
71
114
  */
@@ -1 +1 @@
1
- {"version":3,"file":"packet-stream-server.js","sourceRoot":"","sources":["../../../../src/lib/tunnel/packet-stream-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"packet-stream-server.js","sourceRoot":"","sources":["../../../../src/lib/tunnel/packet-stream-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-ios-remotexpc",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "main": "build/src/index.js",
5
5
  "types": "build/src/index.d.ts",
6
6
  "type": "module",
@@ -186,17 +186,18 @@ async function establishOneTunnel(startResult, packetStreamBaseRef) {
186
186
  try {
187
187
  packetStreamPort = packetStreamBaseRef.value++;
188
188
  const pss = new PacketStreamServer(packetStreamPort);
189
- await pss.start();
190
-
191
- const consumer = pss.getPacketConsumer();
192
- if (consumer && tunnel.addPacketConsumer) {
193
- tunnel.addPacketConsumer(consumer);
194
- log.info(
195
- `Packet stream server for ${device.identifier} on port ${packetStreamPort}`,
196
- );
189
+ if (tunnel.addPacketConsumer && tunnel.removePacketConsumer) {
190
+ pss.bindTunnel(tunnel);
197
191
  } else {
198
- log.warn(`Failed to attach packet consumer for ${device.identifier}`);
192
+ log.warn(
193
+ `Tunnel for ${device.identifier} does not support packet consumers`,
194
+ );
199
195
  }
196
+ await pss.start();
197
+
198
+ log.info(
199
+ `Packet stream server for ${device.identifier} on port ${packetStreamPort}`,
200
+ );
200
201
  packetStreamServers.set(device.identifier, pss);
201
202
  } catch (err) {
202
203
  log.warn(`Failed to start packet stream server for ${device.identifier}: ${err}`);
@@ -357,13 +357,9 @@ async function createTunnelForDevice(device, tlsOptions, packetStreamBaseRef) {
357
357
  try {
358
358
  packetStreamPort = packetStreamBaseRef.value++;
359
359
  const packetStreamServer = new PacketStreamServer(packetStreamPort);
360
+ packetStreamServer.bindTunnel(tunnel);
360
361
  await packetStreamServer.start();
361
362
 
362
- const consumer = packetStreamServer.getPacketConsumer();
363
- if (consumer) {
364
- tunnel.addPacketConsumer(consumer);
365
- }
366
-
367
363
  packetStreamServers.set(udid, packetStreamServer);
368
364
 
369
365
  log.info(`Packet stream server started on port ${packetStreamPort}`);
@@ -3,9 +3,15 @@ import { EventEmitter } from 'node:events';
3
3
  import { type Server, type Socket, createServer } from 'node:net';
4
4
 
5
5
  import { getLogger } from '../logger.js';
6
+ import type { PacketSource } from '../types.js';
6
7
 
7
8
  const log = getLogger('PacketStreamServer');
8
9
 
10
+ type TunnelPacketBinding = Pick<
11
+ PacketSource,
12
+ 'addPacketConsumer' | 'removePacketConsumer'
13
+ >;
14
+
9
15
  /**
10
16
  * Server that exposes packet streaming from a tunnel over TCP
11
17
  * This allows cross-process access to tunnel packet streams
@@ -14,11 +20,23 @@ export class PacketStreamServer extends EventEmitter {
14
20
  private server: Server | null = null;
15
21
  private readonly clients: Set<Socket> = new Set();
16
22
  private packetConsumer: PacketConsumer | null = null;
23
+ private tunnel: TunnelPacketBinding | null = null;
24
+ private attachedToTunnel = false;
17
25
 
18
26
  constructor(private readonly port: number) {
19
27
  super();
20
28
  }
21
29
 
30
+ /**
31
+ * Bind the tunnel whose packet consumer is attached only while TCP clients are connected.
32
+ */
33
+ bindTunnel(tunnel: TunnelPacketBinding): void {
34
+ this.tunnel = tunnel;
35
+ if (this.clients.size > 0) {
36
+ this.attachToTunnel();
37
+ }
38
+ }
39
+
22
40
  /**
23
41
  * Start the packet stream server
24
42
  * @throws {Error} If server is already started
@@ -33,8 +51,6 @@ export class PacketStreamServer extends EventEmitter {
33
51
  });
34
52
  const server = this.server;
35
53
 
36
- this.packetConsumer = this.createPacketConsumer();
37
-
38
54
  await new Promise<void>((resolve, reject) => {
39
55
  server.once('error', reject);
40
56
  server.listen(this.port, resolve);
@@ -43,6 +59,8 @@ export class PacketStreamServer extends EventEmitter {
43
59
  }
44
60
 
45
61
  async stop(): Promise<void> {
62
+ this.detachFromTunnel();
63
+
46
64
  for (const client of this.clients) {
47
65
  client.destroy();
48
66
  }
@@ -58,6 +76,10 @@ export class PacketStreamServer extends EventEmitter {
58
76
  }
59
77
  }
60
78
 
79
+ /**
80
+ * Returns the packet consumer used when attached to a tunnel.
81
+ * Prefer {@link bindTunnel} for lifecycle management.
82
+ */
61
83
  getPacketConsumer(): PacketConsumer | null {
62
84
  return this.packetConsumer;
63
85
  }
@@ -69,17 +91,52 @@ export class PacketStreamServer extends EventEmitter {
69
91
  log.info(`Client connected from ${client.remoteAddress}`);
70
92
  this.clients.add(client);
71
93
 
94
+ if (this.clients.size === 1) {
95
+ this.attachToTunnel();
96
+ }
97
+
98
+ const onClientGone = (): void => {
99
+ this.clients.delete(client);
100
+ if (this.clients.size === 0) {
101
+ this.detachFromTunnel();
102
+ }
103
+ };
104
+
72
105
  client.on('close', () => {
73
106
  log.info(`Client disconnected from ${client.remoteAddress}`);
74
- this.clients.delete(client);
107
+ onClientGone();
75
108
  });
76
109
 
77
110
  client.on('error', (err) => {
78
111
  log.error(`Client error: ${err}`);
79
- this.clients.delete(client);
112
+ onClientGone();
80
113
  });
81
114
  }
82
115
 
116
+ private attachToTunnel(): void {
117
+ if (this.attachedToTunnel || !this.tunnel) {
118
+ return;
119
+ }
120
+
121
+ if (!this.packetConsumer) {
122
+ this.packetConsumer = this.createPacketConsumer();
123
+ }
124
+
125
+ this.tunnel.addPacketConsumer(this.packetConsumer);
126
+ this.attachedToTunnel = true;
127
+ log.debug('Attached packet consumer to tunnel');
128
+ }
129
+
130
+ private detachFromTunnel(): void {
131
+ if (!this.attachedToTunnel || !this.tunnel || !this.packetConsumer) {
132
+ return;
133
+ }
134
+
135
+ this.tunnel.removePacketConsumer(this.packetConsumer);
136
+ this.attachedToTunnel = false;
137
+ log.debug('Detached packet consumer from tunnel');
138
+ }
139
+
83
140
  /**
84
141
  * Create packet consumer that broadcasts packets to all connected clients
85
142
  */