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
@@ -0,0 +1,249 @@
1
+ import { expect } from 'chai';
2
+
3
+ import {
4
+ ensureString,
5
+ escapeXml,
6
+ findFirstReplacementCharacter,
7
+ fixMultipleXmlDeclarations,
8
+ hasUnicodeReplacementCharacter,
9
+ isValidXml,
10
+ isXmlPlistContent,
11
+ trimBeforeXmlDeclaration,
12
+ } from '../../../src/lib/plist/utils.js';
13
+
14
+ describe('Plist Utils', function () {
15
+ describe('ensureString', function () {
16
+ it('should return the input if it is already a string', function () {
17
+ const input = 'test string';
18
+ expect(ensureString(input)).to.equal(input);
19
+ });
20
+
21
+ it('should convert a Buffer to a string', function () {
22
+ const buffer = Buffer.from('test buffer');
23
+ expect(ensureString(buffer)).to.equal('test buffer');
24
+ });
25
+
26
+ it('should handle empty inputs', function () {
27
+ expect(ensureString('')).to.equal('');
28
+ expect(ensureString(Buffer.alloc(0))).to.equal('');
29
+ });
30
+
31
+ it('should handle Unicode characters', function () {
32
+ const unicodeStr = 'こんにちは世界';
33
+ const buffer = Buffer.from(unicodeStr, 'utf8');
34
+ expect(ensureString(buffer)).to.equal(unicodeStr);
35
+ });
36
+ });
37
+
38
+ describe('hasUnicodeReplacementCharacter', function () {
39
+ it('should return true if the string contains replacement characters', function () {
40
+ expect(hasUnicodeReplacementCharacter('test�string')).to.be.true;
41
+ expect(hasUnicodeReplacementCharacter('�at the beginning')).to.be.true;
42
+ expect(hasUnicodeReplacementCharacter('at the end�')).to.be.true;
43
+ expect(hasUnicodeReplacementCharacter('multiple��chars')).to.be.true;
44
+ });
45
+
46
+ it('should return false if the string does not contain replacement characters', function () {
47
+ expect(hasUnicodeReplacementCharacter('normal string')).to.be.false;
48
+ expect(hasUnicodeReplacementCharacter('')).to.be.false;
49
+ });
50
+
51
+ it('should work with Buffer inputs', function () {
52
+ const bufferWithReplacement = Buffer.from('test�string', 'utf8');
53
+ const bufferWithoutReplacement = Buffer.from('normal string', 'utf8');
54
+
55
+ expect(hasUnicodeReplacementCharacter(bufferWithReplacement)).to.be.true;
56
+ expect(hasUnicodeReplacementCharacter(bufferWithoutReplacement)).to.be
57
+ .false;
58
+ });
59
+ });
60
+
61
+ describe('findFirstReplacementCharacter', function () {
62
+ it('should return the index of the first replacement character', function () {
63
+ expect(findFirstReplacementCharacter('test�string')).to.equal(4);
64
+ expect(findFirstReplacementCharacter('�at the beginning')).to.equal(0);
65
+ expect(findFirstReplacementCharacter('multiple��chars')).to.equal(8);
66
+ });
67
+
68
+ it('should return -1 if no replacement character is found', function () {
69
+ expect(findFirstReplacementCharacter('normal string')).to.equal(-1);
70
+ expect(findFirstReplacementCharacter('')).to.equal(-1);
71
+ });
72
+
73
+ it('should work with Buffer inputs', function () {
74
+ const bufferWithReplacement = Buffer.from('test�string', 'utf8');
75
+ expect(findFirstReplacementCharacter(bufferWithReplacement)).to.equal(4);
76
+ });
77
+ });
78
+
79
+ describe('trimBeforeXmlDeclaration', function () {
80
+ it('should remove content before the XML declaration', function () {
81
+ const input = 'garbage data<?xml version="1.0"?><root></root>';
82
+ const expected = '<?xml version="1.0"?><root></root>';
83
+ expect(trimBeforeXmlDeclaration(input)).to.equal(expected);
84
+ });
85
+
86
+ it('should return the original string if there is no XML declaration', function () {
87
+ const input = '<root></root>';
88
+ expect(trimBeforeXmlDeclaration(input)).to.equal(input);
89
+ });
90
+
91
+ it('should return the original string if the XML declaration is at the beginning', function () {
92
+ const input = '<?xml version="1.0"?><root></root>';
93
+ expect(trimBeforeXmlDeclaration(input)).to.equal(input);
94
+ });
95
+
96
+ it('should work with Buffer inputs', function () {
97
+ const buffer = Buffer.from(
98
+ 'garbage data<?xml version="1.0"?><root></root>',
99
+ 'utf8',
100
+ );
101
+ const expected = '<?xml version="1.0"?><root></root>';
102
+ expect(trimBeforeXmlDeclaration(buffer)).to.equal(expected);
103
+ });
104
+
105
+ it('should handle whitespace before the XML declaration', function () {
106
+ const input = ' \n <?xml version="1.0"?><root></root>';
107
+ const expected = '<?xml version="1.0"?><root></root>';
108
+ expect(trimBeforeXmlDeclaration(input)).to.equal(expected);
109
+ });
110
+ });
111
+
112
+ describe('fixMultipleXmlDeclarations', function () {
113
+ it('should remove additional XML declarations', function () {
114
+ const input = '<?xml version="1.0"?><root><?xml version="1.1"?></root>';
115
+ const expected = '<?xml version="1.0"?><root></root>';
116
+ expect(fixMultipleXmlDeclarations(input)).to.equal(expected);
117
+ });
118
+
119
+ it('should handle multiple additional declarations', function () {
120
+ const input =
121
+ '<?xml version="1.0"?><root><?xml version="1.1"?><?xml version="1.2"?></root>';
122
+ const expected = '<?xml version="1.0"?><root></root>';
123
+ expect(fixMultipleXmlDeclarations(input)).to.equal(expected);
124
+ });
125
+
126
+ it('should return the original string if there is only one XML declaration', function () {
127
+ const input = '<?xml version="1.0"?><root></root>';
128
+ expect(fixMultipleXmlDeclarations(input)).to.equal(input);
129
+ });
130
+
131
+ it('should return the original string if there is no XML declaration', function () {
132
+ const input = '<root></root>';
133
+ expect(fixMultipleXmlDeclarations(input)).to.equal(input);
134
+ });
135
+
136
+ it('should work with Buffer inputs', function () {
137
+ const buffer = Buffer.from(
138
+ '<?xml version="1.0"?><root><?xml version="1.1"?></root>',
139
+ 'utf8',
140
+ );
141
+ const expected = '<?xml version="1.0"?><root></root>';
142
+ expect(fixMultipleXmlDeclarations(buffer)).to.equal(expected);
143
+ });
144
+ });
145
+
146
+ describe('isValidXml', function () {
147
+ it('should return true for valid XML', function () {
148
+ expect(isValidXml('<?xml version="1.0"?><root></root>')).to.be.true;
149
+ expect(isValidXml('<root></root>')).to.be.true;
150
+ expect(isValidXml('<root/>')).to.be.true;
151
+ });
152
+
153
+ it('should return false for invalid XML', function () {
154
+ expect(isValidXml('')).to.be.false;
155
+ expect(isValidXml(' ')).to.be.false;
156
+ expect(isValidXml('not xml')).to.be.false;
157
+ });
158
+
159
+ it('should work with Buffer inputs', function () {
160
+ const validBuffer = Buffer.from(
161
+ '<?xml version="1.0"?><root></root>',
162
+ 'utf8',
163
+ );
164
+ const invalidBuffer = Buffer.from('not xml', 'utf8');
165
+
166
+ expect(isValidXml(validBuffer)).to.be.true;
167
+ expect(isValidXml(invalidBuffer)).to.be.false;
168
+ });
169
+
170
+ it('should handle XML with only a declaration', function () {
171
+ expect(isValidXml('<?xml version="1.0"?>')).to.be.true;
172
+ });
173
+ });
174
+
175
+ describe('escapeXml', function () {
176
+ it('should escape special XML characters', function () {
177
+ expect(escapeXml('<')).to.equal('&lt;');
178
+ expect(escapeXml('>')).to.equal('&gt;');
179
+ expect(escapeXml('&')).to.equal('&amp;');
180
+ expect(escapeXml('"')).to.equal('&quot;');
181
+ expect(escapeXml("'")).to.equal('&apos;');
182
+ });
183
+
184
+ it('should escape multiple special characters in a string', function () {
185
+ expect(escapeXml('<tag attr="value" & more=\'stuff\'>')).to.equal(
186
+ '&lt;tag attr=&quot;value&quot; &amp; more=&apos;stuff&apos;&gt;',
187
+ );
188
+ });
189
+
190
+ it('should not modify regular characters', function () {
191
+ expect(escapeXml('normal text')).to.equal('normal text');
192
+ expect(escapeXml('123')).to.equal('123');
193
+ });
194
+
195
+ it('should handle empty strings', function () {
196
+ expect(escapeXml('')).to.equal('');
197
+ });
198
+ });
199
+
200
+ describe('isXmlPlistContent', function () {
201
+ it('should return true for content with XML declaration', function () {
202
+ expect(isXmlPlistContent('<?xml version="1.0"?><root></root>')).to.be
203
+ .true;
204
+ });
205
+
206
+ it('should return true for content with plist tag', function () {
207
+ expect(isXmlPlistContent('<plist><dict></dict></plist>')).to.be.true;
208
+ });
209
+
210
+ it('should return false for content without XML declaration or plist tag', function () {
211
+ expect(isXmlPlistContent('<root></root>')).to.be.false;
212
+ expect(isXmlPlistContent('not xml')).to.be.false;
213
+ });
214
+
215
+ it('should work with Buffer inputs', function () {
216
+ const xmlBuffer = Buffer.from(
217
+ '<?xml version="1.0"?><root></root>',
218
+ 'utf8',
219
+ );
220
+ const plistBuffer = Buffer.from('<plist><dict></dict></plist>', 'utf8');
221
+ const nonXmlBuffer = Buffer.from('not xml', 'utf8');
222
+
223
+ expect(isXmlPlistContent(xmlBuffer)).to.be.true;
224
+ expect(isXmlPlistContent(plistBuffer)).to.be.true;
225
+ expect(isXmlPlistContent(nonXmlBuffer)).to.be.false;
226
+ });
227
+ });
228
+
229
+ describe('Integration Tests', function () {
230
+ it('should clean and fix XML with multiple issues', function () {
231
+ // XML with multiple issues: content before declaration, multiple declarations, and replacement character
232
+ const problematicXml =
233
+ 'garbage data<?xml version="1.0"?><?xml version="1.1"?><plist><dict><key>test</key><string>val�ue</string></dict></plist>';
234
+
235
+ // Apply the cleaning functions in sequence
236
+ let cleanedXml = trimBeforeXmlDeclaration(problematicXml);
237
+ cleanedXml = fixMultipleXmlDeclarations(cleanedXml);
238
+
239
+ // Verify the result
240
+ expect(cleanedXml).to.include('<?xml version="1.0"?>');
241
+ expect(cleanedXml).not.to.include('garbage data');
242
+ expect(cleanedXml).not.to.include('<?xml version="1.1"?>');
243
+ expect(cleanedXml).to.include('<plist>');
244
+
245
+ // Verify that the replacement character is still there (it's handled by the parser, not these utils)
246
+ expect(hasUnicodeReplacementCharacter(cleanedXml)).to.be.true;
247
+ });
248
+ });
249
+ });
@@ -0,0 +1,60 @@
1
+ import { expect } from 'chai';
2
+
3
+ import { parsePlist as parseXmlPlist } from '../../../src/lib/plist/plist-parser.js';
4
+ import type { PlistDictionary } from '../../../src/lib/types.js';
5
+
6
+ describe('XML Cleaning Logic', function () {
7
+ describe('Handling Unicode Replacement Characters', function () {
8
+ it('should handle replacement characters at different positions', function () {
9
+ const xmlAtBeginning =
10
+ '�<?xml version="1.0" encoding="UTF-8"?><plist><dict><key>test</key><string>value</string></dict></plist>';
11
+ const resultBeginning = parseXmlPlist(xmlAtBeginning) as PlistDictionary;
12
+ expect(resultBeginning).to.have.property('test', 'value');
13
+
14
+ const xmlAfterDeclaration =
15
+ '<?xml version="1.0" encoding="UTF-8"?>�<plist><dict><key>test</key><string>value</string></dict></plist>';
16
+ const resultAfterDeclaration = parseXmlPlist(
17
+ xmlAfterDeclaration,
18
+ ) as PlistDictionary;
19
+ expect(resultAfterDeclaration).to.have.property('test', 'value');
20
+
21
+ const xmlAtEnd =
22
+ '<?xml version="1.0" encoding="UTF-8"?><plist><dict><key>test</key><string>value</string></dict></plist>�';
23
+ const resultAtEnd = parseXmlPlist(xmlAtEnd) as PlistDictionary;
24
+ expect(resultAtEnd).to.have.property('test', 'value');
25
+ });
26
+ });
27
+
28
+ describe('Edge Cases', function () {
29
+ it('should handle the case where prevTagPos < 0', function () {
30
+ const xml =
31
+ '�<plist><dict><key>test</key><string>value</string></dict></plist>';
32
+
33
+ const result = parseXmlPlist(xml) as PlistDictionary;
34
+
35
+ expect(result).to.have.property('test', 'value');
36
+ });
37
+
38
+ it('should handle the case where nextTagPos <= prevTagPos', function () {
39
+ const xml =
40
+ '<?xml version="1.0" encoding="UTF-8"?><plist><dict><key>test</key><string>value</string></dict></plist>�';
41
+
42
+ const result = parseXmlPlist(xml) as PlistDictionary;
43
+
44
+ expect(result).to.have.property('test', 'value');
45
+ });
46
+
47
+ it('should handle XML with only replacement characters', function () {
48
+ const xml = '���';
49
+
50
+ try {
51
+ parseXmlPlist(xml);
52
+ expect.fail(
53
+ 'Should have thrown an error for XML with only replacement characters',
54
+ );
55
+ } catch (error) {
56
+ expect(error).to.exist;
57
+ }
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,194 @@
1
+ import { expect } from 'chai';
2
+
3
+ import {
4
+ TunnelRegistryServer,
5
+ startTunnelRegistryServer,
6
+ } from '../../../src/lib/tunnel/tunnel-registry-server.js';
7
+ import type {
8
+ TunnelRegistry,
9
+ TunnelRegistryEntry,
10
+ } from '../../../src/lib/types.js';
11
+
12
+ describe('TunnelRegistryServer', function () {
13
+ let server: TunnelRegistryServer;
14
+ const testPort = 4724;
15
+
16
+ // Test data
17
+ const testRegistry = {
18
+ tunnels: {
19
+ 'test-udid-123': {
20
+ udid: 'test-udid-123',
21
+ deviceId: 1,
22
+ address: '127.0.0.1',
23
+ packetStreamPort: 12345,
24
+ rsdPort: 58783,
25
+ connectionType: 'USB',
26
+ productId: 12345,
27
+ createdAt: Date.now(),
28
+ lastUpdated: Date.now(),
29
+ },
30
+ },
31
+ metadata: {
32
+ lastUpdated: new Date().toISOString(),
33
+ totalTunnels: 1,
34
+ activeTunnels: 1,
35
+ },
36
+ };
37
+
38
+ beforeEach(async function () {
39
+ server = await startTunnelRegistryServer(testRegistry, testPort);
40
+ });
41
+
42
+ afterEach(async function () {
43
+ if (server) {
44
+ await server.stop();
45
+ }
46
+ });
47
+
48
+ describe('GET /remotexpc/tunnels', function () {
49
+ it('should return all tunnels', async function () {
50
+ const response = await fetch(
51
+ `http://localhost:${testPort}/remotexpc/tunnels`,
52
+ );
53
+ const data = (await response.json()) as TunnelRegistry;
54
+
55
+ expect(response.status).to.equal(200);
56
+ expect(data).to.have.property('tunnels');
57
+ expect(data).to.have.property('metadata');
58
+ expect(data.tunnels).to.have.property('test-udid-123');
59
+ });
60
+ });
61
+
62
+ describe('GET /remotexpc/tunnels/:udid', function () {
63
+ it('should return tunnel by UDID', async function () {
64
+ const response = await fetch(
65
+ `http://localhost:${testPort}/remotexpc/tunnels/test-udid-123`,
66
+ );
67
+ const data = (await response.json()) as TunnelRegistryEntry;
68
+
69
+ expect(response.status).to.equal(200);
70
+ expect(data.udid).to.equal('test-udid-123');
71
+ expect(data.deviceId).to.equal(1);
72
+ });
73
+
74
+ it('should return 404 for non-existent UDID', async function () {
75
+ const response = await fetch(
76
+ `http://localhost:${testPort}/remotexpc/tunnels/non-existent`,
77
+ );
78
+ const data = (await response.json()) as { error: string };
79
+
80
+ expect(response.status).to.equal(404);
81
+ expect(data).to.have.property('error');
82
+ expect(data.error).to.include('Tunnel not found');
83
+ });
84
+ });
85
+
86
+ describe('GET /remotexpc/tunnels/device/:deviceId', function () {
87
+ it('should return tunnel by device ID', async function () {
88
+ const response = await fetch(
89
+ `http://localhost:${testPort}/remotexpc/tunnels/device/1`,
90
+ );
91
+ const data = (await response.json()) as TunnelRegistryEntry;
92
+
93
+ expect(response.status).to.equal(200);
94
+ expect(data.udid).to.equal('test-udid-123');
95
+ expect(data.deviceId).to.equal(1);
96
+ });
97
+
98
+ it('should return 404 for non-existent device ID', async function () {
99
+ const response = await fetch(
100
+ `http://localhost:${testPort}/remotexpc/tunnels/device/999`,
101
+ );
102
+ const data = (await response.json()) as { error: string };
103
+
104
+ expect(response.status).to.equal(404);
105
+ expect(data).to.have.property('error');
106
+ expect(data.error).to.include('Tunnel not found');
107
+ });
108
+
109
+ it('should return 400 for invalid device ID', async function () {
110
+ const response = await fetch(
111
+ `http://localhost:${testPort}/remotexpc/tunnels/device/invalid`,
112
+ );
113
+ const data = (await response.json()) as { error: string };
114
+
115
+ expect(response.status).to.equal(400);
116
+ expect(data).to.have.property('error');
117
+ expect(data.error).to.equal('Invalid device ID');
118
+ });
119
+ });
120
+
121
+ describe('PUT /remotexpc/tunnels/:udid', function () {
122
+ it('should update tunnel', async function () {
123
+ const updateData = {
124
+ ...testRegistry.tunnels['test-udid-123'],
125
+ rsdPort: 58784,
126
+ };
127
+
128
+ const response = await fetch(
129
+ `http://localhost:${testPort}/remotexpc/tunnels/test-udid-123`,
130
+ {
131
+ method: 'PUT',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify(updateData),
134
+ },
135
+ );
136
+ const data = (await response.json()) as {
137
+ success: boolean;
138
+ tunnel: TunnelRegistryEntry;
139
+ };
140
+
141
+ expect(response.status).to.equal(200);
142
+ expect(data).to.have.property('success', true);
143
+ expect(data.tunnel.rsdPort).to.equal(58784);
144
+ });
145
+
146
+ it('should return 400 for UDID mismatch', async function () {
147
+ const updateData = {
148
+ ...testRegistry.tunnels['test-udid-123'],
149
+ udid: 'different-udid',
150
+ };
151
+
152
+ const response = await fetch(
153
+ `http://localhost:${testPort}/remotexpc/tunnels/test-udid-123`,
154
+ {
155
+ method: 'PUT',
156
+ headers: { 'Content-Type': 'application/json' },
157
+ body: JSON.stringify(updateData),
158
+ },
159
+ );
160
+ const data = (await response.json()) as { error: string };
161
+
162
+ expect(response.status).to.equal(400);
163
+ expect(data).to.have.property('error');
164
+ expect(data.error).to.include('UDID mismatch');
165
+ });
166
+
167
+ it('should return 400 for invalid JSON', async function () {
168
+ const response = await fetch(
169
+ `http://localhost:${testPort}/remotexpc/tunnels/test-udid-123`,
170
+ {
171
+ method: 'PUT',
172
+ headers: { 'Content-Type': 'application/json' },
173
+ body: 'invalid json',
174
+ },
175
+ );
176
+ const data = (await response.json()) as { error: string };
177
+
178
+ expect(response.status).to.equal(400);
179
+ expect(data).to.have.property('error');
180
+ });
181
+ });
182
+
183
+ describe('Unknown routes', function () {
184
+ it('should return 404 for unknown routes', async function () {
185
+ const response = await fetch(
186
+ `http://localhost:${testPort}/unknown/route`,
187
+ );
188
+ const data = (await response.json()) as { error: string };
189
+
190
+ expect(response.status).to.equal(404);
191
+ expect(data).to.have.property('error', 'Not found');
192
+ });
193
+ });
194
+ });
@@ -0,0 +1,71 @@
1
+ import type { expect as expectType } from 'chai';
2
+ import { Server, Socket } from 'node:net';
3
+
4
+ import { Usbmux } from '../../../src/lib/usbmux/index.js';
5
+ import { UDID, fixtures, getServerWithFixtures } from '../fixtures/index.js';
6
+
7
+ let chai;
8
+ let chaiAsPromised;
9
+ let expect: typeof expectType;
10
+
11
+ before(async function () {
12
+ chai = await import('chai');
13
+ chaiAsPromised = await import('chai-as-promised');
14
+ chai.use(chaiAsPromised.default);
15
+ chai.should();
16
+ expect = chai.expect;
17
+ });
18
+
19
+ describe('usbmux', function () {
20
+ let usbmux: Usbmux | null;
21
+ let server: Server | null;
22
+ let socket: Socket | null;
23
+
24
+ beforeEach(function () {
25
+ usbmux = null;
26
+ server = null;
27
+ socket = null;
28
+ });
29
+
30
+ afterEach(async function () {
31
+ if (usbmux) {
32
+ usbmux.close();
33
+ usbmux = null;
34
+ }
35
+
36
+ // Add a small delay to avoid connection reset errors
37
+ await new Promise((resolve) => setTimeout(resolve, 100));
38
+
39
+ if (server) {
40
+ server.close();
41
+ server = null;
42
+ }
43
+
44
+ socket = null;
45
+ });
46
+
47
+ it('should read usbmux message', async function () {
48
+ ({ server, socket } = await getServerWithFixtures(fixtures.DEVICE_LIST));
49
+ usbmux = new Usbmux(socket);
50
+ const devices = await usbmux.listDevices();
51
+ expect(devices.length).to.equal(1);
52
+ });
53
+
54
+ it('should fail due to timeout', async function () {
55
+ ({ server, socket } = await getServerWithFixtures());
56
+ usbmux = new Usbmux(socket);
57
+
58
+ await expect(usbmux.listDevices(-1)).to.be.rejected;
59
+ });
60
+
61
+ it('should find correct device', async function () {
62
+ ({ server, socket } = await getServerWithFixtures(fixtures.DEVICE_LIST));
63
+ usbmux = new Usbmux(socket);
64
+
65
+ const device = await usbmux.findDevice(UDID);
66
+ expect(device).to.not.be.undefined;
67
+ if (device) {
68
+ expect(device.Properties.SerialNumber).to.equal(UDID);
69
+ }
70
+ });
71
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "target": "es2022",
6
+ "preserveSymlinks": true,
7
+ "rootDir": "./",
8
+
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+
14
+ "outDir": "./build",
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+ "removeComments": false,
18
+ "lib": ["es2023"],
19
+ "types": ["node", "mocha", "chai"],
20
+
21
+ "allowSyntheticDefaultImports": true,
22
+ "noImplicitAny": true,
23
+ "noImplicitThis": false,
24
+ "noUnusedParameters": false,
25
+ "verbatimModuleSyntax": true,
26
+ },
27
+
28
+ "include": [
29
+ "./src/**/*.ts",
30
+ "./test/**/*.ts"
31
+ ],
32
+
33
+ "exclude": [
34
+ "node_modules",
35
+ ]
36
+ }