@appium/coresim 1.13.0 → 1.13.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/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/lib/src/commands/permissions.d.ts.map +1 -1
- package/lib/src/commands/permissions.js +12 -246
- package/lib/src/commands/permissions.js.map +1 -1
- package/lib/src/index.d.ts +1 -1
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js.map +1 -1
- package/lib/src/native-simctl.d.ts +27 -9
- package/lib/src/native-simctl.d.ts.map +1 -1
- package/lib/src/native-simctl.js +9 -16
- package/lib/src/native-simctl.js.map +1 -1
- package/lib/src/utils/bulletin-board-model-v2.d.ts +10 -0
- package/lib/src/utils/bulletin-board-model-v2.d.ts.map +1 -0
- package/lib/src/utils/bulletin-board-model-v2.js +73 -0
- package/lib/src/utils/bulletin-board-model-v2.js.map +1 -0
- package/lib/src/utils/bulletin-board-model-v3.d.ts +4 -0
- package/lib/src/utils/bulletin-board-model-v3.d.ts.map +1 -0
- package/lib/src/utils/bulletin-board-model-v3.js +16 -0
- package/lib/src/utils/bulletin-board-model-v3.js.map +1 -0
- package/lib/src/utils/bulletin-board-model.d.ts +19 -0
- package/lib/src/utils/bulletin-board-model.d.ts.map +1 -0
- package/lib/src/utils/bulletin-board-model.js +2 -0
- package/lib/src/utils/bulletin-board-model.js.map +1 -0
- package/lib/src/utils/bulletin-board-store.d.ts +41 -0
- package/lib/src/utils/bulletin-board-store.d.ts.map +1 -0
- package/lib/src/utils/bulletin-board-store.js +114 -0
- package/lib/src/utils/bulletin-board-store.js.map +1 -0
- package/lib/src/utils/index.d.ts +3 -0
- package/lib/src/utils/index.d.ts.map +1 -1
- package/lib/src/utils/index.js +2 -0
- package/lib/src/utils/index.js.map +1 -1
- package/lib/src/utils/springboard.d.ts +26 -0
- package/lib/src/utils/springboard.d.ts.map +1 -0
- package/lib/src/utils/springboard.js +106 -0
- package/lib/src/utils/springboard.js.map +1 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/@appium+coresim.node +0 -0
- package/src/commands/permissions.ts +13 -263
- package/src/index.ts +1 -1
- package/src/native-simctl.ts +45 -19
- package/src/utils/bulletin-board-model-v2.ts +79 -0
- package/src/utils/bulletin-board-model-v3.ts +16 -0
- package/src/utils/bulletin-board-model.ts +20 -0
- package/src/utils/bulletin-board-store.ts +126 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/springboard.ts +119 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { buildSectionInfo } from './bulletin-board-model-v2.js';
|
|
2
|
+
/** The format used by SpringBoard since iOS 27: v2 with `isLocked` in the `BBSectionInfo`. */
|
|
3
|
+
export const bulletinBoardModelV3 = {
|
|
4
|
+
version: 3,
|
|
5
|
+
createStore: () => ({ sectionInfoVersionNumber: 3 }),
|
|
6
|
+
setNotificationsStatus(store, bundleId, status) {
|
|
7
|
+
const sectionInfo = (store.sectionInfo ??= {});
|
|
8
|
+
if (status === 'unset') {
|
|
9
|
+
delete sectionInfo[bundleId];
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
sectionInfo[bundleId] = buildSectionInfo(bundleId, status, { isLocked: false });
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=bulletin-board-model-v3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulletin-board-model-v3.js","sourceRoot":"","sources":["../../../src/utils/bulletin-board-model-v3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;AAG9D,8FAA8F;AAC9F,MAAM,CAAC,MAAM,oBAAoB,GAAuB;IACtD,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAC,wBAAwB,EAAE,CAAC,EAAC,CAAC;IAClD,sBAAsB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM;QAC5C,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;QAC/C,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,QAAQ,CAAC,GAAG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAC,QAAQ,EAAE,KAAK,EAAC,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type NotificationsStatus = 'granted' | 'denied' | 'critical' | 'unset';
|
|
2
|
+
/** Content of `VersionedSectionInfo.plist`. */
|
|
3
|
+
export interface BulletinBoardStoreContent {
|
|
4
|
+
sectionInfoVersionNumber?: number;
|
|
5
|
+
sectionInfo?: Record<string, Uint8Array>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The data model of one `sectionInfoVersionNumber` of the BulletinBoard store. The store operations
|
|
9
|
+
* (locking, atomic replace, ...) don't depend on it, so a new format version only needs a new model.
|
|
10
|
+
*/
|
|
11
|
+
export interface BulletinBoardModel {
|
|
12
|
+
/** The `sectionInfoVersionNumber` this model reads and writes. */
|
|
13
|
+
readonly version: number;
|
|
14
|
+
/** An empty store, as SpringBoard of this version would start it. */
|
|
15
|
+
createStore(): BulletinBoardStoreContent;
|
|
16
|
+
/** Adds, replaces or (`unset`) removes the app's notifications section in place. */
|
|
17
|
+
setNotificationsStatus(store: BulletinBoardStoreContent, bundleId: string, status: NotificationsStatus): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=bulletin-board-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulletin-board-model.d.ts","sourceRoot":"","sources":["../../../src/utils/bulletin-board-model.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9E,+CAA+C;AAC/C,MAAM,WAAW,yBAAyB;IACxC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,WAAW,IAAI,yBAAyB,CAAC;IACzC,oFAAoF;IACpF,sBAAsB,CAAC,KAAK,EAAE,yBAAyB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC/G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulletin-board-model.js","sourceRoot":"","sources":["../../../src/utils/bulletin-board-model.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NotificationsStatus } from './bulletin-board-model.js';
|
|
2
|
+
/**
|
|
3
|
+
* SpringBoard's BulletinBoard store of a device (`Library/BulletinBoard/VersionedSectionInfo.plist`),
|
|
4
|
+
* which keeps the notifications settings of every app as one archived `BBSectionInfo` per bundle id.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BulletinBoardStore {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
private readonly models;
|
|
9
|
+
/**
|
|
10
|
+
* @param dataPath — data directory of the device
|
|
11
|
+
* @param versions — the `sectionInfoVersionNumber`s supported by the runtime, the oldest first
|
|
12
|
+
* (all the known ones by default). A store of any other version is refused, as the format may
|
|
13
|
+
* change in a future runtime. A missing store (a never-booted device) is created in the first
|
|
14
|
+
* one, as SpringBoard migrates older versions but not newer ones.
|
|
15
|
+
* @throws {Error} if a version is unknown
|
|
16
|
+
*/
|
|
17
|
+
constructor(dataPath: string, versions?: readonly number[]);
|
|
18
|
+
/**
|
|
19
|
+
* Runs `update` once every earlier update of the same store has settled: concurrent updates for
|
|
20
|
+
* different apps would otherwise read the same store, and the last write would drop the other app's
|
|
21
|
+
* section. Only the calls made by this process are serialized.
|
|
22
|
+
*/
|
|
23
|
+
withLock<T>(update: () => Promise<T>): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Adds, replaces or (`unset`) removes the app's section. The store is replaced atomically, so a
|
|
26
|
+
* failed write leaves the previous one intact. A device that has never been booted has no store
|
|
27
|
+
* yet: it is created with just this section, and SpringBoard adds the rest on boot.
|
|
28
|
+
*
|
|
29
|
+
* @returns `false` if there was nothing to write (`unset` without a store)
|
|
30
|
+
* @throws {Error} if the existing store is of an unsupported format version
|
|
31
|
+
*/
|
|
32
|
+
setNotificationsStatus(bundleId: string, status: NotificationsStatus): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Makes the store immutable (`uchg`) so that nothing, including a dying SpringBoard, can rewrite it.
|
|
35
|
+
* Best effort.
|
|
36
|
+
*/
|
|
37
|
+
setImmutable(immutable: boolean): Promise<void>;
|
|
38
|
+
private modelOf;
|
|
39
|
+
private replace;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=bulletin-board-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulletin-board-store.d.ts","sourceRoot":"","sources":["../../../src/utils/bulletin-board-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAgD,mBAAmB,EAAC,MAAM,2BAA2B,CAAC;AAUlH;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IAEvD;;;;;;;OAOG;gBACS,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,MAAM,EAA6C;IAWpG;;;;OAIG;IACG,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgBvD;;;;;;;OAOG;IACG,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkB7F;;;OAGG;IACG,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,OAAO,CAAC,OAAO;YAWD,OAAO;CAWtB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { fs, plist } from '@appium/support';
|
|
6
|
+
import { bulletinBoardModelV2 } from './bulletin-board-model-v2.js';
|
|
7
|
+
import { bulletinBoardModelV3 } from './bulletin-board-model-v3.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
// All the known data models, the oldest first.
|
|
10
|
+
const KNOWN_MODELS = [bulletinBoardModelV2, bulletinBoardModelV3];
|
|
11
|
+
// The last pending update of each store, see BulletinBoardStore.withLock.
|
|
12
|
+
const pendingUpdates = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* SpringBoard's BulletinBoard store of a device (`Library/BulletinBoard/VersionedSectionInfo.plist`),
|
|
15
|
+
* which keeps the notifications settings of every app as one archived `BBSectionInfo` per bundle id.
|
|
16
|
+
*/
|
|
17
|
+
export class BulletinBoardStore {
|
|
18
|
+
path;
|
|
19
|
+
models;
|
|
20
|
+
/**
|
|
21
|
+
* @param dataPath — data directory of the device
|
|
22
|
+
* @param versions — the `sectionInfoVersionNumber`s supported by the runtime, the oldest first
|
|
23
|
+
* (all the known ones by default). A store of any other version is refused, as the format may
|
|
24
|
+
* change in a future runtime. A missing store (a never-booted device) is created in the first
|
|
25
|
+
* one, as SpringBoard migrates older versions but not newer ones.
|
|
26
|
+
* @throws {Error} if a version is unknown
|
|
27
|
+
*/
|
|
28
|
+
constructor(dataPath, versions = KNOWN_MODELS.map(({ version }) => version)) {
|
|
29
|
+
this.models = versions.map((version) => {
|
|
30
|
+
const model = KNOWN_MODELS.find((m) => m.version === version);
|
|
31
|
+
if (!model) {
|
|
32
|
+
throw new Error(`Unknown BulletinBoard store version ${version}`);
|
|
33
|
+
}
|
|
34
|
+
return model;
|
|
35
|
+
});
|
|
36
|
+
this.path = path.join(dataPath, 'Library', 'BulletinBoard', 'VersionedSectionInfo.plist');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Runs `update` once every earlier update of the same store has settled: concurrent updates for
|
|
40
|
+
* different apps would otherwise read the same store, and the last write would drop the other app's
|
|
41
|
+
* section. Only the calls made by this process are serialized.
|
|
42
|
+
*/
|
|
43
|
+
async withLock(update) {
|
|
44
|
+
const result = (pendingUpdates.get(this.path) ?? Promise.resolve()).then(update);
|
|
45
|
+
const settled = result.then(() => { }, () => { });
|
|
46
|
+
pendingUpdates.set(this.path, settled);
|
|
47
|
+
try {
|
|
48
|
+
return await result;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (pendingUpdates.get(this.path) === settled) {
|
|
52
|
+
pendingUpdates.delete(this.path);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Adds, replaces or (`unset`) removes the app's section. The store is replaced atomically, so a
|
|
58
|
+
* failed write leaves the previous one intact. A device that has never been booted has no store
|
|
59
|
+
* yet: it is created with just this section, and SpringBoard adds the rest on boot.
|
|
60
|
+
*
|
|
61
|
+
* @returns `false` if there was nothing to write (`unset` without a store)
|
|
62
|
+
* @throws {Error} if the existing store is of an unsupported format version
|
|
63
|
+
*/
|
|
64
|
+
async setNotificationsStatus(bundleId, status) {
|
|
65
|
+
let store;
|
|
66
|
+
let model;
|
|
67
|
+
if (await fs.exists(this.path)) {
|
|
68
|
+
store = (await plist.parsePlistFile(this.path));
|
|
69
|
+
model = this.modelOf(store);
|
|
70
|
+
}
|
|
71
|
+
else if (status === 'unset') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
await fs.mkdir(path.dirname(this.path), { recursive: true });
|
|
76
|
+
model = this.models[0];
|
|
77
|
+
store = model.createStore();
|
|
78
|
+
}
|
|
79
|
+
model.setNotificationsStatus(store, bundleId, status);
|
|
80
|
+
await this.replace(store);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Makes the store immutable (`uchg`) so that nothing, including a dying SpringBoard, can rewrite it.
|
|
85
|
+
* Best effort.
|
|
86
|
+
*/
|
|
87
|
+
async setImmutable(immutable) {
|
|
88
|
+
try {
|
|
89
|
+
await execFileAsync('chflags', [immutable ? 'uchg' : 'nouchg', this.path]);
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
}
|
|
93
|
+
modelOf(store) {
|
|
94
|
+
const model = this.models.find(({ version }) => version === store.sectionInfoVersionNumber);
|
|
95
|
+
if (!model) {
|
|
96
|
+
throw new Error(`Unsupported version of ${this.path}: sectionInfoVersionNumber is ${store.sectionInfoVersionNumber}, ` +
|
|
97
|
+
`supported: ${this.models.map(({ version }) => version).join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
return model;
|
|
100
|
+
}
|
|
101
|
+
async replace(store) {
|
|
102
|
+
// In the same directory, so that the rename is atomic.
|
|
103
|
+
const tmpPath = `${this.path}.${randomUUID()}.tmp`;
|
|
104
|
+
try {
|
|
105
|
+
await fs.writeFile(tmpPath, plist.createPlist(store, false), { flag: 'wx' });
|
|
106
|
+
await fs.rename(tmpPath, this.path);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
await fs.unlink(tmpPath).catch(() => { });
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=bulletin-board-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulletin-board-store.js","sourceRoot":"","sources":["../../../src/utils/bulletin-board-store.ts"],"names":[],"mappings":"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"}
|
package/lib/src/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { getPkgRoot, PACKAGE_NAME } from './pkg-root.js';
|
|
2
2
|
export { runCatchingAsync, toTypedError } from './run-catching.js';
|
|
3
3
|
export { resolveDisplayPortId, validateDisplayId } from './display.js';
|
|
4
|
+
export { BulletinBoardStore } from './bulletin-board-store.js';
|
|
5
|
+
export type { NotificationsStatus } from './bulletin-board-model.js';
|
|
6
|
+
export { SpringBoard } from './springboard.js';
|
|
4
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AACvD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAC,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AACvD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAC,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC;AACrE,OAAO,EAAC,kBAAkB,EAAC,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EAAC,mBAAmB,EAAC,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC"}
|
package/lib/src/utils/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { getPkgRoot, PACKAGE_NAME } from './pkg-root.js';
|
|
2
2
|
export { runCatchingAsync, toTypedError } from './run-catching.js';
|
|
3
3
|
export { resolveDisplayPortId, validateDisplayId } from './display.js';
|
|
4
|
+
export { BulletinBoardStore } from './bulletin-board-store.js';
|
|
5
|
+
export { SpringBoard } from './springboard.js';
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AACvD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAC,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AACvD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAC,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC;AACrE,OAAO,EAAC,kBAAkB,EAAC,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NativeSimctl } from '../native-simctl.js';
|
|
2
|
+
/**
|
|
3
|
+
* SpringBoard of a booted device: the process that owns the BulletinBoard store, see
|
|
4
|
+
* setNotificationsAccess. The simulator shares the host kernel, so it is signalled directly.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SpringBoard {
|
|
7
|
+
private readonly simctl;
|
|
8
|
+
private readonly udid;
|
|
9
|
+
private pausedPid;
|
|
10
|
+
constructor(simctl: NativeSimctl, udid: string);
|
|
11
|
+
/**
|
|
12
|
+
* Pauses the running SpringBoard so that it cannot rewrite the BulletinBoard store. It can be in
|
|
13
|
+
* the middle of a restart, hence the retries.
|
|
14
|
+
*/
|
|
15
|
+
pause(): Promise<void>;
|
|
16
|
+
/** Lets the paused SpringBoard run again. */
|
|
17
|
+
resume(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Replaces the paused SpringBoard with a new one: asks launchd to stop it (launchd starts a new
|
|
20
|
+
* one right away), resumes it so that it can exit, then waits for the new process.
|
|
21
|
+
*/
|
|
22
|
+
restart(): Promise<void>;
|
|
23
|
+
private pid;
|
|
24
|
+
private stop;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=springboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"springboard.d.ts","sourceRoot":"","sources":["../../../src/utils/springboard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AAKtD;;;GAGG;AACH,qBAAa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,OAAO,CAAC,SAAS,CAAqB;gBAGnB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM;IAG/B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,6CAA6C;IAC7C,MAAM,IAAI,IAAI;IAMd;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBhB,GAAG;YAIH,IAAI;CAwBnB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { once } from 'node:events';
|
|
2
|
+
import { waitForCondition } from 'asyncbox';
|
|
3
|
+
const SPRINGBOARD_SERVICE = 'com.apple.SpringBoard';
|
|
4
|
+
const SPRINGBOARD_RESTART_TIMEOUT_MS = 30000;
|
|
5
|
+
/**
|
|
6
|
+
* SpringBoard of a booted device: the process that owns the BulletinBoard store, see
|
|
7
|
+
* setNotificationsAccess. The simulator shares the host kernel, so it is signalled directly.
|
|
8
|
+
*/
|
|
9
|
+
export class SpringBoard {
|
|
10
|
+
simctl;
|
|
11
|
+
udid;
|
|
12
|
+
pausedPid;
|
|
13
|
+
constructor(simctl, udid) {
|
|
14
|
+
this.simctl = simctl;
|
|
15
|
+
this.udid = udid;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Pauses the running SpringBoard so that it cannot rewrite the BulletinBoard store. It can be in
|
|
19
|
+
* the middle of a restart, hence the retries.
|
|
20
|
+
*/
|
|
21
|
+
async pause() {
|
|
22
|
+
try {
|
|
23
|
+
await waitForCondition(async () => {
|
|
24
|
+
const pid = await this.pid().catch(() => undefined);
|
|
25
|
+
if (pid && sendSignal(pid, 'SIGSTOP')) {
|
|
26
|
+
this.pausedPid = pid;
|
|
27
|
+
}
|
|
28
|
+
return Boolean(this.pausedPid);
|
|
29
|
+
}, { waitMs: SPRINGBOARD_RESTART_TIMEOUT_MS, intervalMs: 300 });
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
if (!this.pausedPid) {
|
|
33
|
+
throw new Error(`${SPRINGBOARD_SERVICE} is not running`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Lets the paused SpringBoard run again. */
|
|
37
|
+
resume() {
|
|
38
|
+
if (this.pausedPid) {
|
|
39
|
+
sendSignal(this.pausedPid, 'SIGCONT');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Replaces the paused SpringBoard with a new one: asks launchd to stop it (launchd starts a new
|
|
44
|
+
* one right away), resumes it so that it can exit, then waits for the new process.
|
|
45
|
+
*/
|
|
46
|
+
async restart() {
|
|
47
|
+
const previousPid = this.pausedPid;
|
|
48
|
+
try {
|
|
49
|
+
await this.stop();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this.resume();
|
|
53
|
+
this.pausedPid = undefined;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await waitForCondition(async () => {
|
|
57
|
+
const pid = await this.pid().catch(() => undefined);
|
|
58
|
+
return Number.isInteger(pid) && pid !== previousPid;
|
|
59
|
+
}, { waitMs: SPRINGBOARD_RESTART_TIMEOUT_MS, intervalMs: 300 });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error(`${SPRINGBOARD_SERVICE} did not restart within ${SPRINGBOARD_RESTART_TIMEOUT_MS}ms`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async pid() {
|
|
66
|
+
return (await this.simctl.listProcesses(this.udid)).find(({ name }) => name === SPRINGBOARD_SERVICE)?.pid;
|
|
67
|
+
}
|
|
68
|
+
async stop() {
|
|
69
|
+
// Bare name: resolved against the guest runtime's own bin dirs, like in listProcesses.
|
|
70
|
+
const proc = await this.simctl.spawnProcess(this.udid, 'launchctl', {
|
|
71
|
+
arguments: ['launchctl', 'stop', SPRINGBOARD_SERVICE],
|
|
72
|
+
});
|
|
73
|
+
let stderr = '';
|
|
74
|
+
proc.stdout.resume();
|
|
75
|
+
proc.stderr.on('data', (chunk) => {
|
|
76
|
+
stderr += chunk;
|
|
77
|
+
});
|
|
78
|
+
const streamError = Promise.race([once(proc.stdout, 'error'), once(proc.stderr, 'error')]).then(([err]) => {
|
|
79
|
+
throw err;
|
|
80
|
+
});
|
|
81
|
+
const [[code, signal]] = await Promise.race([
|
|
82
|
+
Promise.all([once(proc, 'exit'), once(proc.stderr, 'end')]),
|
|
83
|
+
streamError,
|
|
84
|
+
]);
|
|
85
|
+
if (code !== 0) {
|
|
86
|
+
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
87
|
+
throw new Error(`'launchctl stop ${SPRINGBOARD_SERVICE}' failed with ${reason}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @returns `false` if the process no longer exists
|
|
93
|
+
*/
|
|
94
|
+
function sendSignal(pid, name) {
|
|
95
|
+
try {
|
|
96
|
+
process.kill(pid, name);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err?.code === 'ESRCH') {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=springboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"springboard.js","sourceRoot":"","sources":["../../../src/utils/springboard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appium/coresim",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"description": "Native bindings to CoreSimulator.framework for Apple platform simulator control",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/appium/appium-ios/issues"
|
|
@@ -61,5 +61,6 @@
|
|
|
61
61
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
62
62
|
"npm": ">=10"
|
|
63
63
|
},
|
|
64
|
-
"gypfile": true
|
|
64
|
+
"gypfile": true,
|
|
65
|
+
"gitHead": "9dce08cd8d4162196de0504f57500ec18f83b1c8"
|
|
65
66
|
}
|
|
Binary file
|