appium-ios-remotexpc 2.2.4 → 2.3.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 +12 -0
- package/build/src/index.d.ts +1 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js.map +1 -1
- package/build/src/services/ios/afc/codec.d.ts +5 -0
- package/build/src/services/ios/afc/codec.d.ts.map +1 -1
- package/build/src/services/ios/afc/codec.js +10 -0
- package/build/src/services/ios/afc/codec.js.map +1 -1
- package/build/src/services/ios/installation-proxy/index.d.ts.map +1 -1
- package/build/src/services/ios/installation-proxy/index.js +1 -24
- package/build/src/services/ios/installation-proxy/index.js.map +1 -1
- package/build/src/services/ios/zipconduit/constants.d.ts +20 -0
- package/build/src/services/ios/zipconduit/constants.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/constants.js +20 -0
- package/build/src/services/ios/zipconduit/constants.js.map +1 -0
- package/build/src/services/ios/zipconduit/index.d.ts +38 -0
- package/build/src/services/ios/zipconduit/index.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/index.js +148 -0
- package/build/src/services/ios/zipconduit/index.js.map +1 -0
- package/build/src/services/ios/zipconduit/plists.d.ts +46 -0
- package/build/src/services/ios/zipconduit/plists.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/plists.js +87 -0
- package/build/src/services/ios/zipconduit/plists.js.map +1 -0
- package/build/src/services/ios/zipconduit/stream-zip.d.ts +105 -0
- package/build/src/services/ios/zipconduit/stream-zip.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/stream-zip.js +883 -0
- package/build/src/services/ios/zipconduit/stream-zip.js.map +1 -0
- package/build/src/services/ios/zipconduit/zip-reader.d.ts +15 -0
- package/build/src/services/ios/zipconduit/zip-reader.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/zip-reader.js +33 -0
- package/build/src/services/ios/zipconduit/zip-reader.js.map +1 -0
- package/build/src/services/ios/zipconduit/zip-utils.d.ts +27 -0
- package/build/src/services/ios/zipconduit/zip-utils.d.ts.map +1 -0
- package/build/src/services/ios/zipconduit/zip-utils.js +116 -0
- package/build/src/services/ios/zipconduit/zip-utils.js.map +1 -0
- package/build/src/services.d.ts +6 -0
- package/build/src/services.d.ts.map +1 -1
- package/build/src/services.js +14 -0
- package/build/src/services.js.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +6 -0
- package/src/services/ios/afc/codec.ts +15 -0
- package/src/services/ios/installation-proxy/index.ts +1 -42
- package/src/services/ios/zipconduit/constants.ts +30 -0
- package/src/services/ios/zipconduit/index.ts +242 -0
- package/src/services/ios/zipconduit/plists.ts +139 -0
- package/src/services/ios/zipconduit/stream-zip.ts +1082 -0
- package/src/services/ios/zipconduit/zip-reader.ts +48 -0
- package/src/services/ios/zipconduit/zip-utils.ts +185 -0
- package/src/services.ts +19 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
|
|
3
|
+
import { TRANSFER_CHUNK_SIZE } from './constants.js';
|
|
4
|
+
import { StreamZip, type StreamZipEntry } from './stream-zip.js';
|
|
5
|
+
|
|
6
|
+
/** Subset of a ZIP central-directory entry used while streaming an IPA. */
|
|
7
|
+
export type IpaZipEntry = StreamZipEntry;
|
|
8
|
+
|
|
9
|
+
/** Reading surface of an open ZIP archive used by ZipConduit. */
|
|
10
|
+
export type ZipArchive = Pick<StreamZip, 'entries' | 'stream' | 'close'>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Open an IPA archive, run `fn`, and close the handle when done.
|
|
14
|
+
*/
|
|
15
|
+
export async function withZipFile<T>(
|
|
16
|
+
ipaPath: string,
|
|
17
|
+
fn: (zip: ZipArchive) => Promise<T>,
|
|
18
|
+
): Promise<T> {
|
|
19
|
+
const zip = new StreamZip({
|
|
20
|
+
file: ipaPath,
|
|
21
|
+
chunkSize: TRANSFER_CHUNK_SIZE,
|
|
22
|
+
// Do NOT skip the local header read: an entry's payload offset depends on the
|
|
23
|
+
// LOCAL header's extra-field length, which the central directory does not record
|
|
24
|
+
// and which routinely differs from it. Skipping it streams misaligned bytes and
|
|
25
|
+
// the device fails extraction (ExtractionFailed).
|
|
26
|
+
verifyEntryCrc: false,
|
|
27
|
+
skipEntryNameValidation: true,
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
return await fn(zip);
|
|
31
|
+
} finally {
|
|
32
|
+
await zip.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** List all entries from an already-open IPA archive without extracting them. */
|
|
37
|
+
export async function listZipEntries(zip: ZipArchive): Promise<IpaZipEntry[]> {
|
|
38
|
+
const entries = await zip.entries();
|
|
39
|
+
return Object.values(entries);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Open a decompressed readable stream for one zip entry. */
|
|
43
|
+
export async function openZipEntryStream(
|
|
44
|
+
zip: ZipArchive,
|
|
45
|
+
entry: IpaZipEntry,
|
|
46
|
+
): Promise<Readable> {
|
|
47
|
+
return await zip.stream(entry);
|
|
48
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type net from 'node:net';
|
|
2
|
+
import type { Readable } from 'node:stream';
|
|
3
|
+
import { Readable as ReadableStream } from 'node:stream';
|
|
4
|
+
import { crc32 } from 'node:zlib';
|
|
5
|
+
|
|
6
|
+
import { createPlist } from '../../../lib/plist/unified-plist-creator.js';
|
|
7
|
+
import type { PlistDictionary } from '../../../lib/types.js';
|
|
8
|
+
import { writeBufferToSocket } from '../afc/codec.js';
|
|
9
|
+
import {
|
|
10
|
+
METAINF_FILE_NAME,
|
|
11
|
+
TRANSFER_CHUNK_SIZE,
|
|
12
|
+
ZIP_EXTRA_BYTES,
|
|
13
|
+
ZIP_HEADER_LAST_MODIFIED_DATE,
|
|
14
|
+
ZIP_HEADER_LAST_MODIFIED_TIME,
|
|
15
|
+
ZIP_LOCAL_FILE_HEADER_SIGNATURE,
|
|
16
|
+
} from './constants.js';
|
|
17
|
+
import { createMetaInfPlist } from './plists.js';
|
|
18
|
+
|
|
19
|
+
interface ZipLocalHeader {
|
|
20
|
+
signature: number;
|
|
21
|
+
version: number;
|
|
22
|
+
generalPurposeBitFlags: number;
|
|
23
|
+
compressionMethod: number;
|
|
24
|
+
lastModifiedTime: number;
|
|
25
|
+
lastModifiedDate: number;
|
|
26
|
+
crc32: number;
|
|
27
|
+
compressedSize: number;
|
|
28
|
+
uncompressedSize: number;
|
|
29
|
+
fileNameLength: number;
|
|
30
|
+
extraFieldLength: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Encode ZipMetadata as XML plist bytes for META-INF/com.apple.ZipMetadata.plist.
|
|
35
|
+
*/
|
|
36
|
+
export function createMetaInfBytes(
|
|
37
|
+
numFiles: number,
|
|
38
|
+
totalBytes: number,
|
|
39
|
+
): Buffer {
|
|
40
|
+
const metadata = createMetaInfPlist(
|
|
41
|
+
numFiles,
|
|
42
|
+
totalBytes,
|
|
43
|
+
) as unknown as PlistDictionary;
|
|
44
|
+
const plist = createPlist(metadata, false);
|
|
45
|
+
return Buffer.isBuffer(plist) ? plist : Buffer.from(plist, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write a streaming zip_conduit directory entry to the socket.
|
|
50
|
+
*/
|
|
51
|
+
export async function transferDirectory(
|
|
52
|
+
socket: net.Socket,
|
|
53
|
+
dstDirPath: string,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
await writeBufferToSocket(socket, newZipHeaderDir(dstDirPath));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Write a streaming zip_conduit file entry and its uncompressed payload.
|
|
60
|
+
*/
|
|
61
|
+
export async function transferFile(
|
|
62
|
+
socket: net.Socket,
|
|
63
|
+
src: Readable,
|
|
64
|
+
crc32Value: number,
|
|
65
|
+
uncompressedSize: number,
|
|
66
|
+
dstFilePath: string,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
await writeBufferToSocket(
|
|
69
|
+
socket,
|
|
70
|
+
newZipHeader(uncompressedSize, crc32Value, dstFilePath),
|
|
71
|
+
);
|
|
72
|
+
await pipeReadableToSocket(socket, src);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Copy readable data to the socket in large chunks to reduce await/drain overhead.
|
|
77
|
+
*/
|
|
78
|
+
export async function pipeReadableToSocket(
|
|
79
|
+
socket: net.Socket,
|
|
80
|
+
src: Readable,
|
|
81
|
+
chunkSize = TRANSFER_CHUNK_SIZE,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const buf = Buffer.allocUnsafe(chunkSize);
|
|
84
|
+
let offset = 0;
|
|
85
|
+
|
|
86
|
+
for await (const chunk of src) {
|
|
87
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
88
|
+
let dataOffset = 0;
|
|
89
|
+
while (dataOffset < data.length) {
|
|
90
|
+
const space = chunkSize - offset;
|
|
91
|
+
const toCopy = Math.min(space, data.length - dataOffset);
|
|
92
|
+
data.copy(buf, offset, dataOffset, dataOffset + toCopy);
|
|
93
|
+
offset += toCopy;
|
|
94
|
+
dataOffset += toCopy;
|
|
95
|
+
if (offset === chunkSize) {
|
|
96
|
+
await writeBufferToSocket(socket, buf.subarray(0, chunkSize));
|
|
97
|
+
offset = 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (offset > 0) {
|
|
103
|
+
await writeBufferToSocket(socket, buf.subarray(0, offset));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Write the META-INF/ directory header expected by zip_conduit.
|
|
109
|
+
*/
|
|
110
|
+
export async function transferMetaInfDirectory(
|
|
111
|
+
socket: net.Socket,
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
await transferDirectory(socket, 'META-INF/');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Write the synthetic com.apple.ZipMetadata.plist entry.
|
|
118
|
+
*/
|
|
119
|
+
export async function transferMetaInfFile(
|
|
120
|
+
socket: net.Socket,
|
|
121
|
+
numFiles: number,
|
|
122
|
+
totalBytes: number,
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const metaInfBytes = createMetaInfBytes(numFiles, totalBytes);
|
|
125
|
+
const checksum = crc32(metaInfBytes) >>> 0;
|
|
126
|
+
await transferFile(
|
|
127
|
+
socket,
|
|
128
|
+
ReadableStream.from(metaInfBytes),
|
|
129
|
+
checksum,
|
|
130
|
+
metaInfBytes.length,
|
|
131
|
+
`META-INF/${METAINF_FILE_NAME}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function encodeZipLocalHeader(header: ZipLocalHeader): Buffer {
|
|
136
|
+
const buf = Buffer.alloc(30);
|
|
137
|
+
buf.writeUInt32LE(header.signature, 0);
|
|
138
|
+
buf.writeUInt16LE(header.version, 4);
|
|
139
|
+
buf.writeUInt16LE(header.generalPurposeBitFlags, 6);
|
|
140
|
+
buf.writeUInt16LE(header.compressionMethod, 8);
|
|
141
|
+
buf.writeUInt16LE(header.lastModifiedTime, 10);
|
|
142
|
+
buf.writeUInt16LE(header.lastModifiedDate, 12);
|
|
143
|
+
buf.writeUInt32LE(header.crc32 >>> 0, 14);
|
|
144
|
+
buf.writeUInt32LE(header.compressedSize >>> 0, 18);
|
|
145
|
+
buf.writeUInt32LE(header.uncompressedSize >>> 0, 22);
|
|
146
|
+
buf.writeUInt16LE(header.fileNameLength, 26);
|
|
147
|
+
buf.writeUInt16LE(header.extraFieldLength, 28);
|
|
148
|
+
return buf;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function newZipHeaderDir(name: string): Buffer {
|
|
152
|
+
const nameBytes = Buffer.from(name, 'utf8');
|
|
153
|
+
const header = encodeZipLocalHeader({
|
|
154
|
+
signature: ZIP_LOCAL_FILE_HEADER_SIGNATURE,
|
|
155
|
+
version: 20,
|
|
156
|
+
generalPurposeBitFlags: 0,
|
|
157
|
+
compressionMethod: 0,
|
|
158
|
+
lastModifiedTime: ZIP_HEADER_LAST_MODIFIED_TIME,
|
|
159
|
+
lastModifiedDate: ZIP_HEADER_LAST_MODIFIED_DATE,
|
|
160
|
+
crc32: 0,
|
|
161
|
+
compressedSize: 0,
|
|
162
|
+
uncompressedSize: 0,
|
|
163
|
+
fileNameLength: nameBytes.length,
|
|
164
|
+
extraFieldLength: ZIP_EXTRA_BYTES.length,
|
|
165
|
+
});
|
|
166
|
+
return Buffer.concat([header, nameBytes, ZIP_EXTRA_BYTES]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function newZipHeader(size: number, crc32Value: number, name: string): Buffer {
|
|
170
|
+
const nameBytes = Buffer.from(name, 'utf8');
|
|
171
|
+
const header = encodeZipLocalHeader({
|
|
172
|
+
signature: ZIP_LOCAL_FILE_HEADER_SIGNATURE,
|
|
173
|
+
version: 20,
|
|
174
|
+
generalPurposeBitFlags: 0,
|
|
175
|
+
compressionMethod: 0,
|
|
176
|
+
lastModifiedTime: ZIP_HEADER_LAST_MODIFIED_TIME,
|
|
177
|
+
lastModifiedDate: ZIP_HEADER_LAST_MODIFIED_DATE,
|
|
178
|
+
crc32: crc32Value >>> 0,
|
|
179
|
+
compressedSize: size >>> 0,
|
|
180
|
+
uncompressedSize: size >>> 0,
|
|
181
|
+
fileNameLength: nameBytes.length,
|
|
182
|
+
extraFieldLength: ZIP_EXTRA_BYTES.length,
|
|
183
|
+
});
|
|
184
|
+
return Buffer.concat([header, nameBytes, ZIP_EXTRA_BYTES]);
|
|
185
|
+
}
|
package/src/services.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { SpringBoardService } from './services/ios/springboard-service/index.js'
|
|
|
33
33
|
import SyslogService from './services/ios/syslog-service/index.js';
|
|
34
34
|
import { DvtTestmanagedProxyService } from './services/ios/testmanagerd/index.js';
|
|
35
35
|
import { WebInspectorService } from './services/ios/webinspector/index.js';
|
|
36
|
+
import ZipConduitService from './services/ios/zipconduit/index.js';
|
|
36
37
|
|
|
37
38
|
const log = getLogger('Services');
|
|
38
39
|
|
|
@@ -219,6 +220,24 @@ export async function startAfcService(udid: string): Promise<AfcService> {
|
|
|
219
220
|
});
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Start streaming zip_conduit service over RemoteXPC shim.
|
|
225
|
+
* Use {@link ZipConduitService.install} for fast IPA installation without AFC upload.
|
|
226
|
+
*/
|
|
227
|
+
export async function startZipConduitService(
|
|
228
|
+
udid: string,
|
|
229
|
+
): Promise<ZipConduitService> {
|
|
230
|
+
return withRemoteXpcConnection(udid, (remoteXPC, tunnelConnection) => {
|
|
231
|
+
const descriptor = remoteXPC.findService(
|
|
232
|
+
ZipConduitService.RSD_SERVICE_NAME,
|
|
233
|
+
);
|
|
234
|
+
return new ZipConduitService([
|
|
235
|
+
tunnelConnection.host,
|
|
236
|
+
parseInt(descriptor.port, 10),
|
|
237
|
+
]);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
222
241
|
/**
|
|
223
242
|
* Start CrashReportsService over RemoteXPC shim.
|
|
224
243
|
* Resolves the crash report copy mobile and crash mover service ports via RemoteXPC.
|