appium-ios-remotexpc 0.0.1

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 (92) hide show
  1. package/.github/dependabot.yml +38 -0
  2. package/.github/workflows/format-check.yml +43 -0
  3. package/.github/workflows/lint-and-build.yml +40 -0
  4. package/.github/workflows/pr-title.yml +16 -0
  5. package/.github/workflows/publish.js.yml +42 -0
  6. package/.github/workflows/test-validation.yml +40 -0
  7. package/.mocharc.json +8 -0
  8. package/.prettierignore +3 -0
  9. package/.prettierrc +17 -0
  10. package/.releaserc +37 -0
  11. package/CHANGELOG.md +63 -0
  12. package/LICENSE +201 -0
  13. package/README.md +178 -0
  14. package/assets/images/ios-arch.png +0 -0
  15. package/eslint.config.js +45 -0
  16. package/package.json +78 -0
  17. package/scripts/test-tunnel-creation.ts +378 -0
  18. package/src/base-plist-service.ts +83 -0
  19. package/src/base-socket-service.ts +55 -0
  20. package/src/index.ts +34 -0
  21. package/src/lib/apple-tv/constants.ts +83 -0
  22. package/src/lib/apple-tv/errors.ts +31 -0
  23. package/src/lib/apple-tv/tlv/decoder.ts +68 -0
  24. package/src/lib/apple-tv/tlv/encoder.ts +33 -0
  25. package/src/lib/apple-tv/tlv/index.ts +6 -0
  26. package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
  27. package/src/lib/apple-tv/types.ts +58 -0
  28. package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
  29. package/src/lib/apple-tv/utils/index.ts +2 -0
  30. package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
  31. package/src/lib/lockdown/index.ts +468 -0
  32. package/src/lib/pair-record/index.ts +8 -0
  33. package/src/lib/pair-record/pair-record.ts +133 -0
  34. package/src/lib/plist/binary-plist-creator.ts +571 -0
  35. package/src/lib/plist/binary-plist-parser.ts +587 -0
  36. package/src/lib/plist/constants.ts +53 -0
  37. package/src/lib/plist/index.ts +54 -0
  38. package/src/lib/plist/length-based-splitter.ts +326 -0
  39. package/src/lib/plist/plist-creator.ts +42 -0
  40. package/src/lib/plist/plist-decoder.ts +135 -0
  41. package/src/lib/plist/plist-encoder.ts +36 -0
  42. package/src/lib/plist/plist-parser.ts +144 -0
  43. package/src/lib/plist/plist-service.ts +231 -0
  44. package/src/lib/plist/unified-plist-creator.ts +19 -0
  45. package/src/lib/plist/unified-plist-parser.ts +25 -0
  46. package/src/lib/plist/utils.ts +376 -0
  47. package/src/lib/remote-xpc/constants.ts +22 -0
  48. package/src/lib/remote-xpc/handshake-frames.ts +377 -0
  49. package/src/lib/remote-xpc/handshake.ts +152 -0
  50. package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
  51. package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
  52. package/src/lib/tunnel/index.ts +253 -0
  53. package/src/lib/tunnel/packet-stream-client.ts +185 -0
  54. package/src/lib/tunnel/packet-stream-server.ts +133 -0
  55. package/src/lib/tunnel/tunnel-api-client.ts +234 -0
  56. package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
  57. package/src/lib/types.ts +291 -0
  58. package/src/lib/usbmux/index.ts +630 -0
  59. package/src/lib/usbmux/usbmux-decoder.ts +66 -0
  60. package/src/lib/usbmux/usbmux-encoder.ts +55 -0
  61. package/src/service-connection.ts +79 -0
  62. package/src/services/index.ts +15 -0
  63. package/src/services/ios/base-service.ts +81 -0
  64. package/src/services/ios/diagnostic-service/index.ts +241 -0
  65. package/src/services/ios/diagnostic-service/keys.ts +770 -0
  66. package/src/services/ios/syslog-service/index.ts +387 -0
  67. package/src/services/ios/tunnel-service/index.ts +88 -0
  68. package/src/services.ts +81 -0
  69. package/test/integration/diagnostics-test.ts +44 -0
  70. package/test/integration/read-pair-record-test.ts +39 -0
  71. package/test/integration/tunnel-test.ts +104 -0
  72. package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
  73. package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
  74. package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
  75. package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
  76. package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
  77. package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
  78. package/test/unit/fixtures/index.ts +88 -0
  79. package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
  80. package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
  81. package/test/unit/plist/error-handling.spec.ts +101 -0
  82. package/test/unit/plist/fixtures/sample.binary.plist +0 -0
  83. package/test/unit/plist/fixtures/sample.xml.plist +38 -0
  84. package/test/unit/plist/plist-parser.spec.ts +283 -0
  85. package/test/unit/plist/plist.spec.ts +205 -0
  86. package/test/unit/plist/tag-position-handling.spec.ts +90 -0
  87. package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
  88. package/test/unit/plist/utils.spec.ts +249 -0
  89. package/test/unit/plist/xml-cleaning.spec.ts +60 -0
  90. package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
  91. package/test/unit/usbmux/usbmux-specs.ts +71 -0
  92. package/tsconfig.json +36 -0
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # appium-ios-remotexpc
2
+
3
+ A Node.js library for interacting with iOS devices
4
+ through Appium using remote XPC services.
5
+ This library enables communication with iOS devices
6
+ through various services like system logs and network tunneling.
7
+
8
+ ## Overview
9
+
10
+ This library provides functionality for:
11
+
12
+ - Remote XPC (Cross Process Communication) with iOS devices
13
+ - Lockdown communication
14
+ - USB device multiplexing (usbmux)
15
+ - Property list (plist) handling
16
+ - IPv6 tunneling services to iOS devices using TUN/TAP interfaces
17
+ - System log access
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install appium-ios-remotexpc
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Node.js 16 or later
28
+ - iOS device for testing
29
+ - Proper device pairing and trust setup
30
+ - Root/sudo privileges for tunnel creation (TUN/TAP interface requires elevated permissions)
31
+
32
+ ## Features
33
+
34
+ - **Plist Handling**: Encode, decode, parse, and create property lists for iOS device communication.
35
+ - **USB Device Communication**: Connect to iOS devices over USB using the usbmux protocol.
36
+ - **Remote XPC**: Establish Remote XPC connections with iOS devices.
37
+ - **Service Architecture**: Connect to various iOS services:
38
+ - System Log Service: Access device logs
39
+ - Tunnel Service: Network tunneling to/from iOS devices
40
+ - Diagnostic Service: Device diagnostics
41
+ - **Pair Record Management**: Read and write device pairing records.
42
+ - **Packet Streaming**: Stream packets between host and device for service communication.
43
+
44
+ ## Architecture Flow
45
+
46
+ The following diagram illustrates the high-level flow of how the tunnel is created:
47
+
48
+ <div align="center">
49
+ <img src="assets/images/ios-arch.png" alt="iOS Architecture" width="70%">
50
+ </div>
51
+
52
+ ### Role of TUN/TAP
53
+
54
+ The `appium-ios-tuntap (previously tuntap-bridge)` module plays a crucial role in establishing network connectivity:
55
+
56
+ 1. **TLS Socket Input**: Receives the secure TLS socket connection from CoreDeviceProxy
57
+ 2. **Virtual Network Interface**: Creates a TUN/TAP virtual network interface on the host system
58
+ 3. **IPv6 Tunnel**: Establishes an IPv6 tunnel between the host and iOS device
59
+ 4. **Packet Routing**: Routes network packets between the virtual interface and the iOS device
60
+ 5. **Service Access**: Enables access to iOS shim services through the tunnel
61
+
62
+ **Technical Details:**
63
+ - **Platform Support**: Works on both macOS and Linux
64
+ - **IPv6 Support**: Creates IPv6 tunnels for modern iOS communication
65
+ - **Packet Handling**: Manages packet routing between virtual interface and device
66
+ - **Automatic Cleanup**: Properly closes tunnels and cleans up interfaces
67
+
68
+ **Security Considerations:**
69
+ - Requires root/sudo access for TUN/TAP interface creation
70
+ - Uses TLS for secure communication with iOS devices
71
+
72
+ ## Usage
73
+
74
+ ### Creating a Tunnel (Low-level approach)
75
+
76
+ ```typescript
77
+ import { createLockdownServiceByUDID, startCoreDeviceProxy, TunnelManager } from 'appium-ios-remotexpc';
78
+
79
+ // Create lockdown service
80
+ const { lockdownService, device } = await createLockdownServiceByUDID(udid);
81
+
82
+ // Start CoreDeviceProxy
83
+ const { socket } = await startCoreDeviceProxy(
84
+ lockdownService,
85
+ device.DeviceID,
86
+ device.Properties.SerialNumber,
87
+ { rejectUnauthorized: false }
88
+ );
89
+
90
+ // Create tunnel using tuntap
91
+ const tunnel = await TunnelManager.getTunnel(socket);
92
+ console.log(`Tunnel created at ${tunnel.Address} with RSD port ${tunnel.RsdPort}`);
93
+
94
+ // Create RemoteXPC connection
95
+ const remoteXPC = await TunnelManager.createRemoteXPCConnection(
96
+ tunnel.Address,
97
+ tunnel.RsdPort
98
+ );
99
+
100
+ // Access services
101
+ const services = remoteXPC.getServices();
102
+ ```
103
+
104
+ ## Development
105
+
106
+ ### Setup
107
+
108
+ ```bash
109
+ # Clone the repository
110
+ git clone https://github.com/yourusername/appium-ios-remotexpc.git
111
+ cd appium-ios-remotexpc
112
+
113
+ # Install dependencies
114
+ npm install
115
+
116
+ # Build the project
117
+ npm run build
118
+ ```
119
+
120
+ ### Continuous Integration
121
+
122
+ This project uses GitHub Actions for continuous integration and Dependabot for dependency management:
123
+
124
+ - **Lint and Build**: Automatically runs linting and builds the project on Node.js LTS.
125
+ - **Format Check**: Ensures code formatting adheres to project standards
126
+ - **Test Validation**: Validates that test files compile correctly (actual tests require physical devices)
127
+ - **Dependabot**: Automatically creates PRs for dependency updates weekly
128
+
129
+ All pull requests must pass these checks before merging. The workflows are defined in the `.github/workflows` directory.
130
+
131
+ ### Scripts
132
+
133
+ - `npm run build` - Clean and build the project
134
+ - `npm run lint` - Run ESLint
135
+ - `npm run format` - Run prettier
136
+ - `npm run lint:fix` - Run ESLint with auto-fix
137
+ - `npm test` - Run tests (requires sudo privileges for tunneling)
138
+ - `npm run test:tunnel-creation` - Create tunnels for testing (requires sudo)
139
+
140
+ ## Project Structure
141
+
142
+ - `/src` - Source code
143
+ - `/lib` - Core libraries
144
+ - `/lockdown` - Device lockdown protocol
145
+ - `/pair-record` - Pairing record handling
146
+ - `/plist` - Property list processing
147
+ - `/remote-xpc` - XPC connection handling
148
+ - `/tunnel` - Tunneling implementation with tuntap integration
149
+ - `/usbmux` - USB multiplexing protocol
150
+ - `/services` - Service implementations
151
+ - `/ios`
152
+ - `/diagnostic-service` - Device diagnostics
153
+ - `/syslog-service` - System log access
154
+ - `/tunnel-service` - Network tunneling
155
+
156
+ ## Testing
157
+
158
+ ```bash
159
+ # Run all tests
160
+ npm test
161
+ ```
162
+
163
+ Note: Integration tests require:
164
+ - Physical iOS devices connected
165
+ - Sudo privileges for tunnel creation
166
+ - Device trust established
167
+
168
+ ## License
169
+
170
+ Apache-2.0
171
+
172
+ ## Contributing
173
+
174
+ Contributions are welcome! Please feel free to submit a Pull Request.
175
+
176
+ ## Notes
177
+
178
+ This project is under active development. APIs may change without notice.
Binary file
@@ -0,0 +1,45 @@
1
+ import appiumConfig from '@appium/eslint-config-appium-ts';
2
+ import unicorn from 'eslint-plugin-unicorn';
3
+
4
+ export default [
5
+ ...appiumConfig,
6
+ {
7
+ files: ['**/*.ts'],
8
+ linterOptions: {
9
+ reportUnusedDisableDirectives: 'off',
10
+ },
11
+ plugins: {
12
+ unicorn: unicorn,
13
+ },
14
+ rules: {
15
+ quotes: ['error', 'single'],
16
+ semi: ['error', 'always'],
17
+ '@typescript-eslint/no-explicit-any': 'off',
18
+ '@typescript-eslint/member-ordering': [
19
+ 'error',
20
+ {
21
+ default: [
22
+ // ─── PUBLIC METHODS ─────────────────────────────────────────────────────
23
+ 'public-static-method',
24
+ 'public-instance-method',
25
+
26
+ // ─── PROTECTED METHODS ──────────────────────────────────────────────────
27
+ 'protected-static-method',
28
+ 'protected-instance-method',
29
+
30
+ // ─── PRIVATE METHODS ────────────────────────────────────────────────────
31
+ 'private-static-method',
32
+ 'private-instance-method',
33
+ ],
34
+ },
35
+ ],
36
+ 'unicorn/filename-case': [
37
+ 'error',
38
+ {
39
+ case: 'kebabCase',
40
+ },
41
+ ],
42
+ },
43
+ ignores: ['**/build/**', '**/node_modules/**'],
44
+ },
45
+ ];
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "appium-ios-remotexpc",
3
+ "version": "0.0.1",
4
+ "main": "build/src/index.js",
5
+ "types": "build/src/index.d.ts",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./build/src/index.js",
10
+ "types": "./build/src/index.d.ts"
11
+ }
12
+ },
13
+ "engines": {
14
+ "node": ">=20 <23"
15
+ },
16
+ "scripts": {
17
+ "clean:build": "rimraf ./build",
18
+ "build:es": "tsc",
19
+ "build": "run-s clean:* build:*",
20
+ "lint": "eslint src --ext .ts --quiet",
21
+ "format": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
22
+ "format:check": "prettier --check \"{src,test}/**/*.{ts,tsx}\"",
23
+ "lint:fix": "eslint src --ext .ts --fix",
24
+ "test": "mocha test/integration/**/*.ts",
25
+ "test:all": "mocha -r tsx/cjs test/run-integration-tests.ts",
26
+ "test:tunnel": "mocha test/integration/tunnel-test.ts --exit --timeout 1m",
27
+ "test:pair-record": "mocha test/integration/read-pair-record-test.ts --exit --timeout 1m",
28
+ "test:diagnostics": "mocha test/integration/diagnostics-test.ts --exit --timeout 1m",
29
+ "test:unit": "mocha 'test/unit/**/*.ts' --exit --timeout 2m",
30
+ "test:tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
31
+ "test:tunnel-creation:lsof": "sudo tsx scripts/test-tunnel-creation.ts --keep-open"
32
+ },
33
+ "keywords": [],
34
+ "author": "Appium Contributors",
35
+ "license": "Apache-2.0",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/appium/appium-ios-remotexpc.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/appium/appium-ios-remotexpc/issues"
42
+ },
43
+ "description": "",
44
+ "devDependencies": {
45
+ "@appium/eslint-config-appium-ts": "^1.0.4",
46
+ "@appium/tsconfig": "^0.3.5",
47
+ "@eslint/js": "^9.23.0",
48
+ "@semantic-release/changelog": "^6.0.3",
49
+ "@semantic-release/git": "^10.0.1",
50
+ "@trivago/prettier-plugin-sort-imports": "^5.2.2",
51
+ "@types/chai": "^5.2.1",
52
+ "@types/chai-as-promised": "^8.0.2",
53
+ "@types/mocha": "^10.0.10",
54
+ "appium": "^2.17.1",
55
+ "chai": "^5.2.0",
56
+ "chai-as-promised": "^8.0.1",
57
+ "conventional-changelog-conventionalcommits": "^8.0.0",
58
+ "eslint": "^9.23.0",
59
+ "eslint-config-prettier": "^10.1.2",
60
+ "eslint-plugin-unicorn": "^58.0.0",
61
+ "mocha": "^11.1.0",
62
+ "prettier": "^3.5.3",
63
+ "rimraf": "^6.0.1",
64
+ "semantic-release": "^24.0.0",
65
+ "ts-node": "^10.9.2",
66
+ "tsx": "^4.7.0",
67
+ "typescript": "^5.2.2",
68
+ "typescript-eslint": "^8.29.0"
69
+ },
70
+ "dependencies": {
71
+ "@appium/strongbox": "^0.x",
72
+ "@appium/support": "^6.1.0",
73
+ "@types/node": "^24.0.10",
74
+ "@xmldom/xmldom": "^0.9.8",
75
+ "npm-run-all2": "^7.0.2",
76
+ "tuntap-bridge": "^0.x"
77
+ }
78
+ }
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test script for creating lockdown service, starting CoreDeviceProxy, and creating tunnel
4
+ * This script demonstrates the tunnel creation workflow for all connected devices
5
+ */
6
+ import { logger } from '@appium/support';
7
+ import type { ConnectionOptions } from 'tls';
8
+
9
+ import {
10
+ PacketStreamServer,
11
+ SocketInfo,
12
+ TunnelManager,
13
+ createLockdownServiceByUDID,
14
+ createUsbmux,
15
+ startCoreDeviceProxy,
16
+ TunnelRegistry,
17
+ } from '../src/index.js';
18
+ import { startTunnelRegistryServer } from '../src/lib/tunnel/tunnel-registry-server.js';
19
+ import type { Device } from '../src/lib/usbmux/index.js';
20
+
21
+ const log = logger.getLogger('TunnelCreation');
22
+ /**
23
+ * Update tunnel registry with new tunnel information
24
+ */
25
+ async function updateTunnelRegistry(
26
+ results: TunnelResult[],
27
+ ): Promise<TunnelRegistry> {
28
+ const now = Date.now();
29
+ const nowISOString = new Date().toISOString();
30
+
31
+ // Initialize registry if it doesn't exist
32
+ const registry: TunnelRegistry = {
33
+ tunnels: {},
34
+ metadata: {
35
+ lastUpdated: nowISOString,
36
+ totalTunnels: 0,
37
+ activeTunnels: 0,
38
+ }
39
+ };
40
+
41
+ // Update tunnels
42
+ for (const result of results) {
43
+ if (result.success) {
44
+ const udid = result.device.Properties.SerialNumber;
45
+ registry.tunnels[udid] = {
46
+ udid,
47
+ deviceId: result.device.DeviceID,
48
+ address: result.tunnel.Address,
49
+ rsdPort: result.tunnel.RsdPort ?? 0,
50
+ packetStreamPort: result.packetStreamPort,
51
+ connectionType: result.device.Properties.ConnectionType,
52
+ productId: result.device.Properties.ProductID,
53
+ createdAt: registry.tunnels[udid]?.createdAt ?? now,
54
+ lastUpdated: now,
55
+ };
56
+ }
57
+ }
58
+
59
+ // Update metadata
60
+ registry.metadata = {
61
+ lastUpdated: nowISOString,
62
+ totalTunnels: Object.keys(registry.tunnels).length,
63
+ activeTunnels: Object.keys(registry.tunnels).length, // Assuming all are active for now
64
+ };
65
+
66
+ return registry;
67
+ }
68
+
69
+ const activeServers: Array<{ server: any; port: number }> = [];
70
+ const packetStreamServers: Map<string, PacketStreamServer> = new Map();
71
+
72
+ const deviceInfoMap: Map<
73
+ string,
74
+ {
75
+ udid: string;
76
+ address: string;
77
+ rsdPort?: number;
78
+ connectionType: string;
79
+ productId: number;
80
+ }
81
+ > = new Map();
82
+
83
+ let PACKET_STREAM_BASE_PORT = 50000;
84
+ /**
85
+ * Setup cleanup handlers for graceful shutdown
86
+ */
87
+ function setupCleanupHandlers(): void {
88
+ const cleanup = async (signal: string) => {
89
+ log.warn(`\nReceived ${signal}. Cleaning up...`);
90
+
91
+ // Close all packet stream servers
92
+ if (packetStreamServers.size > 0) {
93
+ log.info(
94
+ `Closing ${packetStreamServers.size} packet stream server(s)...`,
95
+ );
96
+ for (const [udid, server] of packetStreamServers) {
97
+ try {
98
+ await server.stop();
99
+ log.info(`Closed packet stream server for device ${udid}`);
100
+ } catch (err) {
101
+ log.warn(
102
+ `Failed to close packet stream server for device ${udid}: ${err}`,
103
+ );
104
+ }
105
+ }
106
+ packetStreamServers.clear();
107
+ }
108
+
109
+ log.info('Cleanup completed. Exiting...');
110
+ process.exit(0);
111
+ };
112
+
113
+ // Handle various termination signals
114
+ process.on('SIGINT', () => cleanup('SIGINT (Ctrl+C)'));
115
+ process.on('SIGTERM', () => cleanup('SIGTERM'));
116
+ process.on('SIGHUP', () => cleanup('SIGHUP'));
117
+
118
+ // Handle uncaught exceptions and unhandled rejections
119
+ process.on('uncaughtException', async (error) => {
120
+ log.error('Uncaught Exception:', error);
121
+ await cleanup('Uncaught Exception');
122
+ process.exit(1);
123
+ });
124
+
125
+ process.on('unhandledRejection', async (reason, promise) => {
126
+ log.error('Unhandled Rejection at:', promise, 'reason:', reason);
127
+ await cleanup('Unhandled Rejection');
128
+ process.exit(1);
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Interface for tunnel result
134
+ */
135
+ interface TunnelResult {
136
+ device: Device;
137
+ tunnel: {
138
+ Address: string;
139
+ RsdPort?: number;
140
+ };
141
+ packetStreamPort?: number;
142
+ success: boolean;
143
+ error?: string;
144
+ }
145
+
146
+ async function createTunnelForDevice(
147
+ device: Device,
148
+ tlsOptions: Partial<ConnectionOptions>,
149
+ ): Promise<TunnelResult & { socket?: any; socketInfo?: SocketInfo }> {
150
+ const udid = device.Properties.SerialNumber;
151
+
152
+ try {
153
+ log.info(`\n--- Processing device: ${udid} ---`);
154
+ log.info(`Device ID: ${device.DeviceID}`);
155
+ log.info(`Connection Type: ${device.Properties.ConnectionType}`);
156
+ log.info(`Product ID: ${device.Properties.ProductID}`);
157
+
158
+ log.info('Creating lockdown service...');
159
+ const { lockdownService, device: lockdownDevice } =
160
+ await createLockdownServiceByUDID(udid);
161
+ log.info(
162
+ `Lockdown service created for device: ${lockdownDevice.Properties.SerialNumber}`,
163
+ );
164
+
165
+ log.info('Starting CoreDeviceProxy...');
166
+ const { socket } = await startCoreDeviceProxy(
167
+ lockdownService,
168
+ lockdownDevice.DeviceID,
169
+ lockdownDevice.Properties.SerialNumber,
170
+ tlsOptions,
171
+ );
172
+ log.info('CoreDeviceProxy started successfully');
173
+
174
+ log.info('Creating tunnel...');
175
+ const tunnel = await TunnelManager.getTunnel(socket);
176
+ log.info(
177
+ `Tunnel created for address: ${tunnel.Address} with RsdPort: ${tunnel.RsdPort}`,
178
+ );
179
+
180
+ let packetStreamPort: number | undefined;
181
+ try {
182
+ packetStreamPort = PACKET_STREAM_BASE_PORT++;
183
+ const packetStreamServer = new PacketStreamServer(packetStreamPort);
184
+ await packetStreamServer.start();
185
+
186
+ const consumer = packetStreamServer.getPacketConsumer();
187
+ if (consumer) {
188
+ tunnel.addPacketConsumer(consumer);
189
+ }
190
+
191
+ packetStreamServers.set(udid, packetStreamServer);
192
+
193
+ log.info(`Packet stream server started on port ${packetStreamPort}`);
194
+ } catch (err) {
195
+ log.warn(`Failed to start packet stream server: ${err}`);
196
+ }
197
+
198
+ log.info(`āœ… Tunnel creation completed successfully for device: ${udid}`);
199
+ log.info(` Tunnel Address: ${tunnel.Address}`);
200
+ log.info(` Tunnel RsdPort: ${tunnel.RsdPort}`);
201
+ if (packetStreamPort) {
202
+ log.info(` Packet Stream Port: ${packetStreamPort}`);
203
+ }
204
+
205
+ try {
206
+ if (socket && typeof socket === 'object' && socket.setNoDelay) {
207
+ socket.setNoDelay(true);
208
+ }
209
+
210
+ const deviceInfo = {
211
+ udid: device.Properties.SerialNumber,
212
+ address: tunnel.Address,
213
+ rsdPort: tunnel.RsdPort,
214
+ connectionType: device.Properties.ConnectionType,
215
+ productId: device.Properties.ProductID,
216
+ };
217
+
218
+ deviceInfoMap.set(device.Properties.SerialNumber, deviceInfo);
219
+
220
+ return {
221
+ device,
222
+ tunnel: {
223
+ Address: tunnel.Address,
224
+ RsdPort: tunnel.RsdPort,
225
+ },
226
+ packetStreamPort,
227
+ success: true,
228
+ socket
229
+ };
230
+ } catch (err) {
231
+ log.warn(`Could not add device to info server: ${err}`);
232
+
233
+ return {
234
+ device,
235
+ tunnel: {
236
+ Address: tunnel.Address,
237
+ RsdPort: tunnel.RsdPort,
238
+ },
239
+ packetStreamPort,
240
+ success: true,
241
+ socket,
242
+ };
243
+ }
244
+ } catch (error) {
245
+ const errorMessage = `Failed to create tunnel for device ${udid}: ${error}`;
246
+ log.error(`āŒ ${errorMessage}`);
247
+ return {
248
+ device,
249
+ tunnel: { Address: '', RsdPort: 0 },
250
+ success: false,
251
+ error: errorMessage,
252
+ };
253
+ }
254
+ }
255
+
256
+ /**
257
+ */
258
+ async function main(): Promise<void> {
259
+ setupCleanupHandlers();
260
+
261
+ const args = process.argv.slice(2);
262
+ const keepOpenFlag = args.includes('--keep-open') || args.includes('-k');
263
+ const specificUdid = args.find((arg) => !arg.startsWith('-'));
264
+
265
+ if (specificUdid) {
266
+ log.info(
267
+ `Starting tunnel creation test for specific UDID: ${specificUdid}`,
268
+ );
269
+ } else {
270
+ log.info('Starting tunnel creation test for all connected devices');
271
+ }
272
+
273
+ if (keepOpenFlag) {
274
+ log.info('Running in "keep connections open" mode for lsof inspection');
275
+ }
276
+
277
+ try {
278
+ const tlsOptions: Partial<ConnectionOptions> = {
279
+ rejectUnauthorized: false,
280
+ minVersion: 'TLSv1.2',
281
+ };
282
+
283
+ log.info('Connecting to usbmuxd...');
284
+ const usbmux = await createUsbmux();
285
+
286
+ log.info('Listing all connected devices...');
287
+ const devices = await usbmux.listDevices();
288
+
289
+ await usbmux.close();
290
+
291
+ if (devices.length === 0) {
292
+ log.warn(
293
+ 'No devices found. Make sure iOS devices are connected and trusted.',
294
+ );
295
+ process.exit(0);
296
+ }
297
+
298
+ log.info(`Found ${devices.length} connected device(s):`);
299
+ devices.forEach((device, index) => {
300
+ log.info(` ${index + 1}. UDID: ${device.Properties.SerialNumber}`);
301
+ log.info(` Device ID: ${device.DeviceID}`);
302
+ log.info(` Connection: ${device.Properties.ConnectionType}`);
303
+ log.info(` Product ID: ${device.Properties.ProductID}`);
304
+ });
305
+
306
+ let devicesToProcess = devices;
307
+ if (specificUdid) {
308
+ devicesToProcess = devices.filter(
309
+ (device) => device.Properties.SerialNumber === specificUdid,
310
+ );
311
+
312
+ if (devicesToProcess.length === 0) {
313
+ log.error(
314
+ `Device with UDID ${specificUdid} not found in connected devices.`,
315
+ );
316
+ log.error('Available devices:');
317
+ devices.forEach((device) => {
318
+ log.error(` - ${device.Properties.SerialNumber}`);
319
+ });
320
+ process.exit(1);
321
+ }
322
+ }
323
+
324
+ log.info(`\nProcessing ${devicesToProcess.length} device(s)...`);
325
+
326
+ const results: TunnelResult[] = [];
327
+
328
+ for (const device of devicesToProcess) {
329
+ const result = await createTunnelForDevice(device, tlsOptions);
330
+ results.push(result);
331
+
332
+ if (devicesToProcess.length > 1) {
333
+ await new Promise((resolve) => setTimeout(resolve, 1000));
334
+ }
335
+ }
336
+
337
+ log.info('\n=== TUNNEL CREATION SUMMARY ===');
338
+ const successful = results.filter((r) => r.success);
339
+ const failed = results.filter((r) => !r.success);
340
+
341
+ log.info(`Total devices processed: ${results.length}`);
342
+ log.info(`Successful tunnels: ${successful.length}`);
343
+ log.info(`Failed tunnels: ${failed.length}`);
344
+
345
+ if (successful.length > 0) {
346
+ log.info('\nāœ… Successful tunnels:');
347
+ const registry = await updateTunnelRegistry(results);
348
+ await startTunnelRegistryServer(registry);
349
+
350
+ log.info('\nšŸ“ Tunnel registry API:');
351
+ log.info(' The tunnel registry is now available through the API at:');
352
+ log.info(' http://localhost:42314/remotexpc/tunnels');
353
+ log.info('\n Available endpoints:');
354
+ log.info(' - GET /remotexpc/tunnels - List all tunnels');
355
+ log.info(' - GET /remotexpc/tunnels/:udid - Get tunnel by UDID');
356
+ log.info(' - GET /remotexpc/tunnels/metadata - Get registry metadata');
357
+
358
+ log.info('\nšŸ’” Example usage:');
359
+ log.info(' curl http://localhost:4723/remotexpc/tunnels');
360
+ log.info(' curl http://localhost:4723/remotexpc/tunnels/metadata');
361
+ if (successful.length > 0) {
362
+ const firstUdid = successful[0].device.Properties.SerialNumber;
363
+ log.info(
364
+ ` curl http://localhost:4723/remotexpc/tunnels/${firstUdid}`,
365
+ );
366
+ }
367
+ }
368
+ } catch (error) {
369
+ log.error(`Error during tunnel creation test: ${error}`);
370
+ process.exit(1);
371
+ }
372
+ }
373
+
374
+ // Run the main function
375
+ main().catch(async (error) => {
376
+ log.error(`Fatal error: ${error}`);
377
+ process.exit(1);
378
+ });