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.
- package/.github/dependabot.yml +38 -0
- package/.github/workflows/format-check.yml +43 -0
- package/.github/workflows/lint-and-build.yml +40 -0
- package/.github/workflows/pr-title.yml +16 -0
- package/.github/workflows/publish.js.yml +42 -0
- package/.github/workflows/test-validation.yml +40 -0
- package/.mocharc.json +8 -0
- package/.prettierignore +3 -0
- package/.prettierrc +17 -0
- package/.releaserc +37 -0
- package/CHANGELOG.md +63 -0
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/assets/images/ios-arch.png +0 -0
- package/eslint.config.js +45 -0
- package/package.json +78 -0
- package/scripts/test-tunnel-creation.ts +378 -0
- package/src/base-plist-service.ts +83 -0
- package/src/base-socket-service.ts +55 -0
- package/src/index.ts +34 -0
- package/src/lib/apple-tv/constants.ts +83 -0
- package/src/lib/apple-tv/errors.ts +31 -0
- package/src/lib/apple-tv/tlv/decoder.ts +68 -0
- package/src/lib/apple-tv/tlv/encoder.ts +33 -0
- package/src/lib/apple-tv/tlv/index.ts +6 -0
- package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
- package/src/lib/apple-tv/types.ts +58 -0
- package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
- package/src/lib/apple-tv/utils/index.ts +2 -0
- package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
- package/src/lib/lockdown/index.ts +468 -0
- package/src/lib/pair-record/index.ts +8 -0
- package/src/lib/pair-record/pair-record.ts +133 -0
- package/src/lib/plist/binary-plist-creator.ts +571 -0
- package/src/lib/plist/binary-plist-parser.ts +587 -0
- package/src/lib/plist/constants.ts +53 -0
- package/src/lib/plist/index.ts +54 -0
- package/src/lib/plist/length-based-splitter.ts +326 -0
- package/src/lib/plist/plist-creator.ts +42 -0
- package/src/lib/plist/plist-decoder.ts +135 -0
- package/src/lib/plist/plist-encoder.ts +36 -0
- package/src/lib/plist/plist-parser.ts +144 -0
- package/src/lib/plist/plist-service.ts +231 -0
- package/src/lib/plist/unified-plist-creator.ts +19 -0
- package/src/lib/plist/unified-plist-parser.ts +25 -0
- package/src/lib/plist/utils.ts +376 -0
- package/src/lib/remote-xpc/constants.ts +22 -0
- package/src/lib/remote-xpc/handshake-frames.ts +377 -0
- package/src/lib/remote-xpc/handshake.ts +152 -0
- package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
- package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
- package/src/lib/tunnel/index.ts +253 -0
- package/src/lib/tunnel/packet-stream-client.ts +185 -0
- package/src/lib/tunnel/packet-stream-server.ts +133 -0
- package/src/lib/tunnel/tunnel-api-client.ts +234 -0
- package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
- package/src/lib/types.ts +291 -0
- package/src/lib/usbmux/index.ts +630 -0
- package/src/lib/usbmux/usbmux-decoder.ts +66 -0
- package/src/lib/usbmux/usbmux-encoder.ts +55 -0
- package/src/service-connection.ts +79 -0
- package/src/services/index.ts +15 -0
- package/src/services/ios/base-service.ts +81 -0
- package/src/services/ios/diagnostic-service/index.ts +241 -0
- package/src/services/ios/diagnostic-service/keys.ts +770 -0
- package/src/services/ios/syslog-service/index.ts +387 -0
- package/src/services/ios/tunnel-service/index.ts +88 -0
- package/src/services.ts +81 -0
- package/test/integration/diagnostics-test.ts +44 -0
- package/test/integration/read-pair-record-test.ts +39 -0
- package/test/integration/tunnel-test.ts +104 -0
- package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
- package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
- package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
- package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
- package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
- package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
- package/test/unit/fixtures/index.ts +88 -0
- package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
- package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
- package/test/unit/plist/error-handling.spec.ts +101 -0
- package/test/unit/plist/fixtures/sample.binary.plist +0 -0
- package/test/unit/plist/fixtures/sample.xml.plist +38 -0
- package/test/unit/plist/plist-parser.spec.ts +283 -0
- package/test/unit/plist/plist.spec.ts +205 -0
- package/test/unit/plist/tag-position-handling.spec.ts +90 -0
- package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
- package/test/unit/plist/utils.spec.ts +249 -0
- package/test/unit/plist/xml-cleaning.spec.ts +60 -0
- package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
- package/test/unit/usbmux/usbmux-specs.ts +71 -0
- 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
|
package/eslint.config.js
ADDED
|
@@ -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
|
+
});
|