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 +6 -0
- package/build/src/lib/tunnel/packet-stream-server.d.ts +15 -0
- package/build/src/lib/tunnel/packet-stream-server.d.ts.map +1 -1
- package/build/src/lib/tunnel/packet-stream-server.js +46 -3
- package/build/src/lib/tunnel/packet-stream-server.js.map +1 -1
- package/package.json +1 -1
- package/scripts/start-appletv-tunnel.mjs +10 -9
- package/scripts/tunnel-creation.mjs +1 -5
- package/src/lib/tunnel/packet-stream-server.ts +61 -4
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;
|
|
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
|
-
|
|
86
|
+
onClientGone();
|
|
63
87
|
});
|
|
64
88
|
client.on('error', (err) => {
|
|
65
89
|
log.error(`Client error: ${err}`);
|
|
66
|
-
|
|
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":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAA4B,YAAY,EAAE,MAAM,UAAU,CAAC;AAElE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
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
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
107
|
+
onClientGone();
|
|
75
108
|
});
|
|
76
109
|
|
|
77
110
|
client.on('error', (err) => {
|
|
78
111
|
log.error(`Client error: ${err}`);
|
|
79
|
-
|
|
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
|
*/
|