appium-ios-remotexpc 0.8.0 → 0.10.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -0
  3. package/build/src/index.d.ts +2 -1
  4. package/build/src/index.d.ts.map +1 -1
  5. package/build/src/index.js +1 -0
  6. package/build/src/lib/types.d.ts +30 -0
  7. package/build/src/lib/types.d.ts.map +1 -1
  8. package/build/src/lib/types.js +2 -0
  9. package/build/src/services/index.d.ts +3 -1
  10. package/build/src/services/index.d.ts.map +1 -1
  11. package/build/src/services/index.js +3 -1
  12. package/build/src/services/ios/afc/codec.d.ts +46 -0
  13. package/build/src/services/ios/afc/codec.d.ts.map +1 -0
  14. package/build/src/services/ios/afc/codec.js +263 -0
  15. package/build/src/services/ios/afc/constants.d.ts +11 -0
  16. package/build/src/services/ios/afc/constants.d.ts.map +1 -0
  17. package/build/src/services/ios/afc/constants.js +22 -0
  18. package/build/src/services/ios/afc/enums.d.ts +66 -0
  19. package/build/src/services/ios/afc/enums.d.ts.map +1 -0
  20. package/build/src/services/ios/afc/enums.js +70 -0
  21. package/build/src/services/ios/afc/index.d.ts +72 -0
  22. package/build/src/services/ios/afc/index.d.ts.map +1 -0
  23. package/build/src/services/ios/afc/index.js +385 -0
  24. package/build/src/services/ios/afc/stream-utils.d.ts +14 -0
  25. package/build/src/services/ios/afc/stream-utils.d.ts.map +1 -0
  26. package/build/src/services/ios/afc/stream-utils.js +60 -0
  27. package/build/src/services/ios/power-assertion/index.d.ts +40 -0
  28. package/build/src/services/ios/power-assertion/index.d.ts.map +1 -0
  29. package/build/src/services/ios/power-assertion/index.js +64 -0
  30. package/build/src/services.d.ts +8 -1
  31. package/build/src/services.d.ts.map +1 -1
  32. package/build/src/services.js +25 -0
  33. package/package.json +3 -1
  34. package/src/index.ts +4 -0
  35. package/src/lib/types.ts +34 -0
  36. package/src/services/index.ts +4 -0
  37. package/src/services/ios/afc/codec.ts +365 -0
  38. package/src/services/ios/afc/constants.ts +29 -0
  39. package/src/services/ios/afc/enums.ts +70 -0
  40. package/src/services/ios/afc/index.ts +511 -0
  41. package/src/services/ios/afc/stream-utils.ts +102 -0
  42. package/src/services/ios/power-assertion/index.ts +100 -0
  43. package/src/services.ts +33 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Enums for AFC protocol as defined by Apple's AFC service
3
+ */
4
+ export var AfcOpcode;
5
+ (function (AfcOpcode) {
6
+ AfcOpcode[AfcOpcode["STATUS"] = 1] = "STATUS";
7
+ AfcOpcode[AfcOpcode["DATA"] = 2] = "DATA";
8
+ AfcOpcode[AfcOpcode["READ_DIR"] = 3] = "READ_DIR";
9
+ AfcOpcode[AfcOpcode["REMOVE_PATH"] = 8] = "REMOVE_PATH";
10
+ AfcOpcode[AfcOpcode["MAKE_DIR"] = 9] = "MAKE_DIR";
11
+ AfcOpcode[AfcOpcode["GET_FILE_INFO"] = 10] = "GET_FILE_INFO";
12
+ AfcOpcode[AfcOpcode["GET_DEVINFO"] = 11] = "GET_DEVINFO";
13
+ AfcOpcode[AfcOpcode["FILE_OPEN"] = 13] = "FILE_OPEN";
14
+ AfcOpcode[AfcOpcode["READ"] = 15] = "READ";
15
+ AfcOpcode[AfcOpcode["WRITE"] = 16] = "WRITE";
16
+ AfcOpcode[AfcOpcode["FILE_CLOSE"] = 20] = "FILE_CLOSE";
17
+ AfcOpcode[AfcOpcode["RENAME_PATH"] = 24] = "RENAME_PATH";
18
+ AfcOpcode[AfcOpcode["MAKE_LINK"] = 28] = "MAKE_LINK";
19
+ })(AfcOpcode || (AfcOpcode = {}));
20
+ export var AfcError;
21
+ (function (AfcError) {
22
+ AfcError[AfcError["SUCCESS"] = 0] = "SUCCESS";
23
+ AfcError[AfcError["UNKNOWN_ERROR"] = 1] = "UNKNOWN_ERROR";
24
+ AfcError[AfcError["OP_HEADER_INVALID"] = 2] = "OP_HEADER_INVALID";
25
+ AfcError[AfcError["NO_RESOURCES"] = 3] = "NO_RESOURCES";
26
+ AfcError[AfcError["READ_ERROR"] = 4] = "READ_ERROR";
27
+ AfcError[AfcError["WRITE_ERROR"] = 5] = "WRITE_ERROR";
28
+ AfcError[AfcError["UNKNOWN_PACKET_TYPE"] = 6] = "UNKNOWN_PACKET_TYPE";
29
+ AfcError[AfcError["INVALID_ARG"] = 7] = "INVALID_ARG";
30
+ AfcError[AfcError["OBJECT_NOT_FOUND"] = 8] = "OBJECT_NOT_FOUND";
31
+ AfcError[AfcError["OBJECT_IS_DIR"] = 9] = "OBJECT_IS_DIR";
32
+ AfcError[AfcError["PERM_DENIED"] = 10] = "PERM_DENIED";
33
+ AfcError[AfcError["SERVICE_NOT_CONNECTED"] = 11] = "SERVICE_NOT_CONNECTED";
34
+ AfcError[AfcError["OP_TIMEOUT"] = 12] = "OP_TIMEOUT";
35
+ AfcError[AfcError["TOO_MUCH_DATA"] = 13] = "TOO_MUCH_DATA";
36
+ AfcError[AfcError["END_OF_DATA"] = 14] = "END_OF_DATA";
37
+ AfcError[AfcError["OP_NOT_SUPPORTED"] = 15] = "OP_NOT_SUPPORTED";
38
+ AfcError[AfcError["OBJECT_EXISTS"] = 16] = "OBJECT_EXISTS";
39
+ AfcError[AfcError["OBJECT_BUSY"] = 17] = "OBJECT_BUSY";
40
+ AfcError[AfcError["NO_SPACE_LEFT"] = 18] = "NO_SPACE_LEFT";
41
+ AfcError[AfcError["OP_WOULD_BLOCK"] = 19] = "OP_WOULD_BLOCK";
42
+ AfcError[AfcError["IO_ERROR"] = 20] = "IO_ERROR";
43
+ AfcError[AfcError["OP_INTERRUPTED"] = 21] = "OP_INTERRUPTED";
44
+ AfcError[AfcError["OP_IN_PROGRESS"] = 22] = "OP_IN_PROGRESS";
45
+ AfcError[AfcError["INTERNAL_ERROR"] = 23] = "INTERNAL_ERROR";
46
+ AfcError[AfcError["MUX_ERROR"] = 30] = "MUX_ERROR";
47
+ AfcError[AfcError["NO_MEM"] = 31] = "NO_MEM";
48
+ AfcError[AfcError["NOT_ENOUGH_DATA"] = 32] = "NOT_ENOUGH_DATA";
49
+ AfcError[AfcError["DIR_NOT_EMPTY"] = 33] = "DIR_NOT_EMPTY";
50
+ })(AfcError || (AfcError = {}));
51
+ export var AfcLinkType;
52
+ (function (AfcLinkType) {
53
+ AfcLinkType[AfcLinkType["HARDLINK"] = 1] = "HARDLINK";
54
+ AfcLinkType[AfcLinkType["SYMLINK"] = 2] = "SYMLINK";
55
+ })(AfcLinkType || (AfcLinkType = {}));
56
+ export var AfcFopenMode;
57
+ (function (AfcFopenMode) {
58
+ AfcFopenMode[AfcFopenMode["RDONLY"] = 1] = "RDONLY";
59
+ AfcFopenMode[AfcFopenMode["RW"] = 2] = "RW";
60
+ AfcFopenMode[AfcFopenMode["WRONLY"] = 3] = "WRONLY";
61
+ AfcFopenMode[AfcFopenMode["WR"] = 4] = "WR";
62
+ AfcFopenMode[AfcFopenMode["APPEND"] = 5] = "APPEND";
63
+ AfcFopenMode[AfcFopenMode["RDAPPEND"] = 6] = "RDAPPEND";
64
+ })(AfcFopenMode || (AfcFopenMode = {}));
65
+ export var AfcFileMode;
66
+ (function (AfcFileMode) {
67
+ AfcFileMode["S_IFDIR"] = "S_IFDIR";
68
+ AfcFileMode["S_IFREG"] = "S_IFREG";
69
+ AfcFileMode["S_IFLNK"] = "S_IFLNK";
70
+ })(AfcFileMode || (AfcFileMode = {}));
@@ -0,0 +1,72 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import { AFC_FOPEN_TEXTUAL_MODES } from './constants.js';
3
+ import { AfcFileMode } from './enums.js';
4
+ export interface StatInfo {
5
+ st_ifmt: AfcFileMode;
6
+ st_size: bigint;
7
+ st_blocks: number;
8
+ st_mtime: Date;
9
+ st_birthtime: Date;
10
+ st_nlink: number;
11
+ LinkTarget?: string;
12
+ [k: string]: any;
13
+ }
14
+ /**
15
+ * AFC client over RSD (Remote XPC shim).
16
+ * After RSDCheckin, speaks raw AFC protocol on the same socket.
17
+ */
18
+ export declare class AfcService {
19
+ private readonly address;
20
+ static readonly RSD_SERVICE_NAME = "com.apple.afc.shim.remote";
21
+ private socket;
22
+ private packetNum;
23
+ private silent;
24
+ constructor(address: [string, number], silent?: boolean);
25
+ /**
26
+ * List directory entries. Returned entries do not include '.' and '..'
27
+ */
28
+ listdir(dirPath: string): Promise<string[]>;
29
+ stat(filePath: string): Promise<StatInfo>;
30
+ isdir(filePath: string): Promise<boolean>;
31
+ exists(filePath: string): Promise<boolean>;
32
+ fopen(filePath: string, mode?: keyof typeof AFC_FOPEN_TEXTUAL_MODES): Promise<bigint>;
33
+ fclose(handle: bigint): Promise<void>;
34
+ createReadStream(handle: bigint, size: bigint): Readable;
35
+ createWriteStream(handle: bigint, chunkSize?: number): Writable;
36
+ fread(handle: bigint, size: bigint): Promise<Buffer>;
37
+ fwrite(handle: bigint, data: Buffer, chunkSize?: number): Promise<void>;
38
+ getFileContents(filePath: string): Promise<Buffer>;
39
+ setFileContents(filePath: string, data: Buffer): Promise<void>;
40
+ readToStream(filePath: string): Promise<Readable>;
41
+ writeFromStream(filePath: string, stream: Readable): Promise<void>;
42
+ pull(remoteSrc: string, localDst: string): Promise<void>;
43
+ rmSingle(filePath: string, force?: boolean): Promise<boolean>;
44
+ rm(filePath: string, force?: boolean): Promise<string[]>;
45
+ rename(src: string, dst: string): Promise<void>;
46
+ push(localSrc: string, remoteDst: string): Promise<void>;
47
+ walk(root: string): Promise<Array<{
48
+ dir: string;
49
+ dirs: string[];
50
+ files: string[];
51
+ }>>;
52
+ /**
53
+ * Close the underlying socket
54
+ */
55
+ close(): void;
56
+ /**
57
+ * Connect to RSD port and perform RSDCheckin.
58
+ * Keeps the underlying socket for raw AFC I/O.
59
+ */
60
+ private _connect;
61
+ private _resolvePath;
62
+ private _dispatch;
63
+ private _receive;
64
+ /**
65
+ * Send a single-operation request and parse result.
66
+ * Throws if status != SUCCESS.
67
+ * Returns response DATA buffer when applicable.
68
+ */
69
+ private _doOperation;
70
+ }
71
+ export default AfcService;
72
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/afc/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,385 @@
1
+ import { logger } from '@appium/support';
2
+ import fs from 'node:fs';
3
+ import net from 'node:net';
4
+ import path from 'node:path';
5
+ import { Readable, Writable } from 'node:stream';
6
+ import { pipeline } from 'node:stream/promises';
7
+ import { buildClosePayload, buildFopenPayload, buildReadPayload, buildRemovePayload, buildRenamePayload, buildStatPayload, nanosecondsToMilliseconds, nextReadChunkSize, parseCStringArray, parseKeyValueNullList, readAfcResponse, rsdHandshakeForRawService, sendAfcPacket, writeUInt64LE, } from './codec.js';
8
+ import { AFC_FOPEN_TEXTUAL_MODES, AFC_WRITE_THIS_LENGTH } from './constants.js';
9
+ import { AfcError, AfcFileMode, AfcOpcode } from './enums.js';
10
+ import { createAfcReadStream, createAfcWriteStream } from './stream-utils.js';
11
+ const log = logger.getLogger('AfcService');
12
+ const NON_LISTABLE_ENTRIES = ['', '.', '..'];
13
+ /**
14
+ * AFC client over RSD (Remote XPC shim).
15
+ * After RSDCheckin, speaks raw AFC protocol on the same socket.
16
+ */
17
+ export class AfcService {
18
+ address;
19
+ static RSD_SERVICE_NAME = 'com.apple.afc.shim.remote';
20
+ socket = null;
21
+ packetNum = 0n;
22
+ silent = false;
23
+ constructor(address, silent) {
24
+ this.address = address;
25
+ this.silent = silent ?? process.env.NODE_ENV !== 'test';
26
+ }
27
+ /**
28
+ * List directory entries. Returned entries do not include '.' and '..'
29
+ */
30
+ async listdir(dirPath) {
31
+ const data = await this._doOperation(AfcOpcode.READ_DIR, buildStatPayload(dirPath));
32
+ const entries = parseCStringArray(data);
33
+ return entries.filter((x) => !NON_LISTABLE_ENTRIES.includes(x));
34
+ }
35
+ async stat(filePath) {
36
+ log.debug(`Getting file info for: ${filePath}`);
37
+ try {
38
+ const data = await this._doOperation(AfcOpcode.GET_FILE_INFO, buildStatPayload(filePath));
39
+ const kv = parseKeyValueNullList(data);
40
+ const out = {
41
+ st_size: BigInt(kv.st_size),
42
+ st_blocks: Number.parseInt(kv.st_blocks, 10),
43
+ st_mtime: new Date(nanosecondsToMilliseconds(kv.st_mtime)),
44
+ st_birthtime: new Date(nanosecondsToMilliseconds(kv.st_birthtime)),
45
+ st_nlink: Number.parseInt(kv.st_nlink, 10),
46
+ };
47
+ for (const [k, v] of Object.entries(kv)) {
48
+ if (!(k in out)) {
49
+ out[k] = v;
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ catch (error) {
55
+ if (!this.silent) {
56
+ log.error(`Failed to stat file '${filePath}':`, error);
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+ async isdir(filePath) {
62
+ const st = await this.stat(filePath);
63
+ return st.st_ifmt === AfcFileMode.S_IFDIR;
64
+ }
65
+ async exists(filePath) {
66
+ try {
67
+ await this.stat(filePath);
68
+ return true;
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
74
+ async fopen(filePath, mode = 'r') {
75
+ const afcMode = AFC_FOPEN_TEXTUAL_MODES[mode];
76
+ if (!afcMode) {
77
+ const allowedModes = Object.keys(AFC_FOPEN_TEXTUAL_MODES).join(', ');
78
+ if (!this.silent) {
79
+ log.error(`Invalid fopen mode '${mode}'. Allowed modes: ${allowedModes}`);
80
+ }
81
+ throw new Error(`Invalid fopen mode '${mode}'. Allowed: ${allowedModes}`);
82
+ }
83
+ log.debug(`Opening file '${filePath}' with mode '${mode}'`);
84
+ try {
85
+ const data = await this._doOperation(AfcOpcode.FILE_OPEN, buildFopenPayload(afcMode, filePath));
86
+ // Response data contains UInt64LE 'handle'
87
+ const handle = data.readBigUInt64LE(0);
88
+ log.debug(`File opened successfully, handle: ${handle}`);
89
+ return handle;
90
+ }
91
+ catch (error) {
92
+ if (!this.silent) {
93
+ log.error(`Failed to open file '${filePath}' with mode '${mode}':`, error);
94
+ }
95
+ throw error;
96
+ }
97
+ }
98
+ async fclose(handle) {
99
+ await this._doOperation(AfcOpcode.FILE_CLOSE, buildClosePayload(handle));
100
+ }
101
+ createReadStream(handle, size) {
102
+ return createAfcReadStream(handle, size, this._dispatch.bind(this), this._receive.bind(this));
103
+ }
104
+ createWriteStream(handle, chunkSize) {
105
+ return createAfcWriteStream(handle, this._dispatch.bind(this), this._receive.bind(this), chunkSize);
106
+ }
107
+ async fread(handle, size) {
108
+ log.debug(`Reading ${size} bytes from handle ${handle}`);
109
+ const stream = this.createReadStream(handle, size);
110
+ const chunks = [];
111
+ for await (const chunk of stream) {
112
+ chunks.push(chunk);
113
+ }
114
+ const buffer = Buffer.concat(chunks);
115
+ log.debug(`Successfully read ${buffer.length} bytes`);
116
+ return buffer;
117
+ }
118
+ async fwrite(handle, data, chunkSize = data.length) {
119
+ log.debug(`Writing ${data.length} bytes to handle ${handle}`);
120
+ const effectiveChunkSize = chunkSize;
121
+ let offset = 0;
122
+ let chunkCount = 0;
123
+ while (offset < data.length) {
124
+ const end = Math.min(offset + effectiveChunkSize, data.length);
125
+ const chunk = data.subarray(offset, end);
126
+ chunkCount++;
127
+ await this._dispatch(AfcOpcode.WRITE, Buffer.concat([writeUInt64LE(handle), chunk]), AFC_WRITE_THIS_LENGTH);
128
+ const { status } = await this._receive();
129
+ if (status !== AfcError.SUCCESS) {
130
+ const errorName = AfcError[status] || 'UNKNOWN';
131
+ if (!this.silent) {
132
+ log.error(`Write operation failed at offset ${offset} with status ${errorName} (${status})`);
133
+ }
134
+ throw new Error(`fwrite chunk failed with ${errorName} (${status}) at offset ${offset}`);
135
+ }
136
+ offset = end;
137
+ }
138
+ log.debug(`Successfully wrote ${data.length} bytes in ${chunkCount} chunks`);
139
+ }
140
+ async getFileContents(filePath) {
141
+ log.debug(`Reading file contents: ${filePath}`);
142
+ const resolved = await this._resolvePath(filePath);
143
+ const st = await this.stat(resolved);
144
+ if (st.st_ifmt !== AfcFileMode.S_IFREG) {
145
+ if (!this.silent) {
146
+ log.error(`Path '${resolved}' is not a regular file (type: ${st.st_ifmt})`);
147
+ }
148
+ throw new Error(`'${resolved}' isn't a regular file`);
149
+ }
150
+ const h = await this.fopen(resolved, 'r');
151
+ try {
152
+ const buf = await this.fread(h, st.st_size);
153
+ log.debug(`Successfully read ${buf.length} bytes from ${filePath}`);
154
+ return buf;
155
+ }
156
+ finally {
157
+ await this.fclose(h);
158
+ }
159
+ }
160
+ async setFileContents(filePath, data) {
161
+ log.debug(`Writing ${data.length} bytes to file: ${filePath}`);
162
+ const h = await this.fopen(filePath, 'w');
163
+ try {
164
+ await this.fwrite(h, data);
165
+ log.debug(`Successfully wrote file: ${filePath}`);
166
+ }
167
+ catch (error) {
168
+ await this.rmSingle(filePath, true);
169
+ throw error;
170
+ }
171
+ finally {
172
+ await this.fclose(h);
173
+ }
174
+ }
175
+ async readToStream(filePath) {
176
+ log.debug(`Creating read stream for: ${filePath}`);
177
+ const resolved = await this._resolvePath(filePath);
178
+ const st = await this.stat(resolved);
179
+ if (st.st_ifmt !== AfcFileMode.S_IFREG) {
180
+ throw new Error(`'${resolved}' isn't a regular file`);
181
+ }
182
+ const handle = await this.fopen(resolved, 'r');
183
+ const stream = this.createReadStream(handle, st.st_size);
184
+ stream.once('close', () => this.fclose(handle).catch(() => { }));
185
+ return stream;
186
+ }
187
+ async writeFromStream(filePath, stream) {
188
+ log.debug(`Writing stream to file: ${filePath}`);
189
+ const handle = await this.fopen(filePath, 'w');
190
+ const writeStream = this.createWriteStream(handle);
191
+ try {
192
+ await pipeline(stream, writeStream);
193
+ log.debug(`Successfully wrote file: ${filePath}`);
194
+ }
195
+ catch (error) {
196
+ await this.rmSingle(filePath, true);
197
+ throw error;
198
+ }
199
+ finally {
200
+ await this.fclose(handle);
201
+ }
202
+ }
203
+ async pull(remoteSrc, localDst) {
204
+ log.debug(`Pulling file from '${remoteSrc}' to '${localDst}'`);
205
+ const stream = await this.readToStream(remoteSrc);
206
+ const writeStream = fs.createWriteStream(localDst);
207
+ await pipeline(stream, writeStream);
208
+ log.debug(`Successfully pulled file to '${localDst}'`);
209
+ }
210
+ async rmSingle(filePath, force = false) {
211
+ log.debug(`Removing single path: ${filePath} (force: ${force})`);
212
+ try {
213
+ await this._doOperation(AfcOpcode.REMOVE_PATH, buildRemovePayload(filePath));
214
+ log.debug(`Successfully removed: ${filePath}`);
215
+ return true;
216
+ }
217
+ catch (error) {
218
+ if (force) {
219
+ log.debug(`Failed to remove '${filePath}' (ignored due to force=true):`, error);
220
+ return false;
221
+ }
222
+ if (!this.silent) {
223
+ log.error(`Failed to remove '${filePath}':`, error);
224
+ }
225
+ throw error;
226
+ }
227
+ }
228
+ async rm(filePath, force = false) {
229
+ if (!(await this.exists(filePath))) {
230
+ return force ? [] : [filePath];
231
+ }
232
+ if (!(await this.isdir(filePath))) {
233
+ if (await this.rmSingle(filePath, force)) {
234
+ return [];
235
+ }
236
+ return [filePath];
237
+ }
238
+ const failedPaths = [];
239
+ for (const entry of await this.listdir(filePath)) {
240
+ const cur = path.posix.join(filePath, entry);
241
+ if (await this.isdir(cur)) {
242
+ const sub = await this.rm(cur, true);
243
+ failedPaths.push(...sub);
244
+ }
245
+ else {
246
+ if (!(await this.rmSingle(cur, true))) {
247
+ failedPaths.push(cur);
248
+ }
249
+ }
250
+ }
251
+ try {
252
+ if (!(await this.rmSingle(filePath, force))) {
253
+ failedPaths.push(filePath);
254
+ }
255
+ }
256
+ catch (err) {
257
+ if (failedPaths.length) {
258
+ failedPaths.push(filePath);
259
+ }
260
+ else {
261
+ throw err;
262
+ }
263
+ }
264
+ return failedPaths;
265
+ }
266
+ async rename(src, dst) {
267
+ log.debug(`Renaming '${src}' to '${dst}'`);
268
+ try {
269
+ await this._doOperation(AfcOpcode.RENAME_PATH, buildRenamePayload(src, dst));
270
+ log.debug(`Successfully renamed '${src}' to '${dst}'`);
271
+ }
272
+ catch (error) {
273
+ if (!this.silent) {
274
+ log.error(`Failed to rename '${src}' to '${dst}':`, error);
275
+ }
276
+ throw error;
277
+ }
278
+ }
279
+ async push(localSrc, remoteDst) {
280
+ log.debug(`Pushing file from '${localSrc}' to '${remoteDst}'`);
281
+ const readStream = fs.createReadStream(localSrc);
282
+ await this.writeFromStream(remoteDst, readStream);
283
+ log.debug(`Successfully pushed file to '${remoteDst}'`);
284
+ }
285
+ async walk(root) {
286
+ const out = [];
287
+ const entries = await this.listdir(root);
288
+ const dirs = [];
289
+ const files = [];
290
+ for (const e of entries) {
291
+ const p = path.posix.join(root, e);
292
+ if (await this.isdir(p)) {
293
+ dirs.push(e);
294
+ }
295
+ else {
296
+ files.push(e);
297
+ }
298
+ }
299
+ out.push({ dir: root, dirs, files });
300
+ for (const d of dirs) {
301
+ out.push(...(await this.walk(path.posix.join(root, d))));
302
+ }
303
+ return out;
304
+ }
305
+ /**
306
+ * Close the underlying socket
307
+ */
308
+ close() {
309
+ log.debug('Closing AFC service connection');
310
+ try {
311
+ this.socket?.end();
312
+ }
313
+ catch (error) {
314
+ log.debug('Error while closing socket (ignored):', error);
315
+ }
316
+ this.socket = null;
317
+ }
318
+ /**
319
+ * Connect to RSD port and perform RSDCheckin.
320
+ * Keeps the underlying socket for raw AFC I/O.
321
+ */
322
+ async _connect() {
323
+ if (this.socket && !this.socket.destroyed) {
324
+ return this.socket;
325
+ }
326
+ const [host, rsdPort] = this.address;
327
+ this.socket = await new Promise((resolve, reject) => {
328
+ const s = net.createConnection({ host, port: rsdPort }, () => {
329
+ s.setTimeout(0);
330
+ s.setKeepAlive(true);
331
+ resolve(s);
332
+ });
333
+ s.once('error', reject);
334
+ s.setTimeout(30000, () => {
335
+ s.destroy();
336
+ reject(new Error('AFC connect timed out'));
337
+ });
338
+ });
339
+ await rsdHandshakeForRawService(this.socket);
340
+ log.debug('RSD handshake complete; switching to raw AFC');
341
+ return this.socket;
342
+ }
343
+ async _resolvePath(filePath) {
344
+ const info = await this.stat(filePath);
345
+ if (info.st_ifmt === AfcFileMode.S_IFLNK && info.LinkTarget) {
346
+ const target = info.LinkTarget;
347
+ if (target.startsWith('/')) {
348
+ return target;
349
+ }
350
+ return path.posix.join(path.posix.dirname(filePath), target);
351
+ }
352
+ return filePath;
353
+ }
354
+ async _dispatch(op, payload = Buffer.alloc(0), thisLenOverride) {
355
+ const sock = await this._connect();
356
+ await sendAfcPacket(sock, op, this.packetNum++, payload, thisLenOverride);
357
+ }
358
+ async _receive() {
359
+ const sock = await this._connect();
360
+ const res = await readAfcResponse(sock);
361
+ return { status: res.status, data: res.data };
362
+ }
363
+ /**
364
+ * Send a single-operation request and parse result.
365
+ * Throws if status != SUCCESS.
366
+ * Returns response DATA buffer when applicable.
367
+ */
368
+ async _doOperation(op, payload = Buffer.alloc(0), thisLenOverride) {
369
+ await this._dispatch(op, payload, thisLenOverride);
370
+ const { status, data } = await this._receive();
371
+ if (status !== AfcError.SUCCESS) {
372
+ const errorName = AfcError[status] || 'UNKNOWN';
373
+ const opName = AfcOpcode[op] || op.toString();
374
+ if (status === AfcError.OBJECT_NOT_FOUND) {
375
+ throw new Error(`AFC error: OBJECT_NOT_FOUND for operation ${opName}`);
376
+ }
377
+ if (!this.silent) {
378
+ log.error(`AFC operation ${opName} failed with status ${errorName} (${status})`);
379
+ }
380
+ throw new Error(`AFC operation ${opName} failed with ${errorName} (${status})`);
381
+ }
382
+ return data;
383
+ }
384
+ }
385
+ export default AfcService;
@@ -0,0 +1,14 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import { AfcError, AfcOpcode } from './enums.js';
3
+ type AfcDispatcher = (op: AfcOpcode, payload: Buffer) => Promise<void>;
4
+ type AfcWriteDispatcher = (op: AfcOpcode, payload: Buffer, thisLenOverride?: number) => Promise<void>;
5
+ export declare function createAfcReadStream(handle: bigint, size: bigint, dispatch: AfcDispatcher, receive: () => Promise<{
6
+ status: AfcError;
7
+ data: Buffer;
8
+ }>): Readable;
9
+ export declare function createAfcWriteStream(handle: bigint, dispatch: AfcWriteDispatcher, receive: () => Promise<{
10
+ status: AfcError;
11
+ data: Buffer;
12
+ }>, chunkSize?: number): Writable;
13
+ export {};
14
+ //# sourceMappingURL=stream-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-utils.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/afc/stream-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEjD,KAAK,aAAa,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE,KAAK,kBAAkB,GAAG,CACxB,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,MAAM,EACf,eAAe,CAAC,EAAE,MAAM,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,MAAM,OAAO,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,QAAQ,CAmCV;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,MAAM,OAAO,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,SAAS,CAAC,EAAE,MAAM,GACjB,QAAQ,CAwCV"}
@@ -0,0 +1,60 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import { buildReadPayload, nextReadChunkSize, writeUInt64LE } from './codec.js';
3
+ import { AFC_WRITE_THIS_LENGTH } from './constants.js';
4
+ import { AfcError, AfcOpcode } from './enums.js';
5
+ export function createAfcReadStream(handle, size, dispatch, receive) {
6
+ let left = size;
7
+ let totalRead = 0n;
8
+ return new Readable({
9
+ async read() {
10
+ try {
11
+ if (left <= 0n) {
12
+ this.push(null);
13
+ return;
14
+ }
15
+ const toRead = nextReadChunkSize(left);
16
+ await dispatch(AfcOpcode.READ, buildReadPayload(handle, toRead));
17
+ const { status, data } = await receive();
18
+ if (status !== AfcError.SUCCESS) {
19
+ const errorName = AfcError[status] || 'UNKNOWN';
20
+ this.destroy(new Error(`fread error: ${errorName} (${status})`));
21
+ return;
22
+ }
23
+ totalRead += BigInt(data.length);
24
+ left -= BigInt(data.length);
25
+ this.push(data);
26
+ if (BigInt(data.length) < toRead) {
27
+ this.push(null);
28
+ }
29
+ }
30
+ catch (error) {
31
+ this.destroy(error);
32
+ }
33
+ },
34
+ });
35
+ }
36
+ export function createAfcWriteStream(handle, dispatch, receive, chunkSize) {
37
+ return new Writable({
38
+ async write(chunk, encoding, callback) {
39
+ try {
40
+ let offset = 0;
41
+ while (offset < chunk.length) {
42
+ const end = Math.min(offset + (chunkSize ?? chunk.length), chunk.length);
43
+ const subchunk = chunk.subarray(offset, end);
44
+ await dispatch(AfcOpcode.WRITE, Buffer.concat([writeUInt64LE(handle), subchunk]), AFC_WRITE_THIS_LENGTH);
45
+ const { status } = await receive();
46
+ if (status !== AfcError.SUCCESS) {
47
+ const errorName = AfcError[status] || 'UNKNOWN';
48
+ callback(new Error(`fwrite chunk failed with ${errorName} (${status}) at offset ${offset}`));
49
+ return;
50
+ }
51
+ offset = end;
52
+ }
53
+ callback();
54
+ }
55
+ catch (error) {
56
+ callback(error);
57
+ }
58
+ },
59
+ });
60
+ }
@@ -0,0 +1,40 @@
1
+ import type { PowerAssertionService as PowerAssertionServiceInterface } from '../../../lib/types.js';
2
+ import { BaseService } from '../base-service.js';
3
+ /**
4
+ * Power assertion types that can be used to prevent system sleep
5
+ */
6
+ export declare enum PowerAssertionType {
7
+ WIRELESS_SYNC = "AMDPowerAssertionTypeWirelessSync",
8
+ PREVENT_USER_IDLE_SYSTEM_SLEEP = "PreventUserIdleSystemSleep",
9
+ PREVENT_SYSTEM_SLEEP = "PreventSystemSleep"
10
+ }
11
+ /**
12
+ * Options for power assertion creation
13
+ */
14
+ export interface PowerAssertionOptions {
15
+ type: PowerAssertionType;
16
+ name: string;
17
+ timeout: number;
18
+ details?: string;
19
+ }
20
+ /**
21
+ * PowerAssertionService provides an API to create power assertions.
22
+ */
23
+ declare class PowerAssertionService extends BaseService implements PowerAssertionServiceInterface {
24
+ static readonly RSD_SERVICE_NAME = "com.apple.mobile.assertion_agent.shim.remote";
25
+ private _conn;
26
+ /**
27
+ * Create a power assertion to prevent system sleep
28
+ * @param options Options for creating the power assertion
29
+ * @returns Promise that resolves when the assertion is created
30
+ */
31
+ createPowerAssertion(options: PowerAssertionOptions): Promise<void>;
32
+ /**
33
+ * Close the connection to the power assertion service
34
+ */
35
+ close(): Promise<void>;
36
+ private connectToPowerAssertionService;
37
+ private buildCreateAssertionRequest;
38
+ }
39
+ export { PowerAssertionService };
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/power-assertion/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,qBAAqB,IAAI,8BAA8B,EACxD,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,aAAa,sCAAsC;IACnD,8BAA8B,+BAA+B;IAC7D,oBAAoB,uBAAuB;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,cAAM,qBACJ,SAAQ,WACR,YAAW,8BAA8B;IAEzC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,kDACiB;IAEjD,OAAO,CAAC,KAAK,CAAkC;IAE/C;;;;OAIG;IACG,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQd,8BAA8B;IAW5C,OAAO,CAAC,2BAA2B;CAgBpC;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC"}