@swipt/cli 0.1.0-nightly.47 → 0.1.0-nightly.49
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/coredevice.mjs +103 -40
- package/output.mjs +61 -0
- package/package.json +6 -4
- package/sideload/trust.js +188 -174
- package/sideload.mjs +597 -183
- package/swipt.mjs +20 -10
package/sideload/trust.js
CHANGED
|
@@ -1,235 +1,249 @@
|
|
|
1
|
-
const [id] = args;
|
|
1
|
+
const [id, mode, waitsForUnlock] = args;
|
|
2
|
+
const name = "Swipt";
|
|
2
3
|
const settings = "com.apple.Preferences";
|
|
4
|
+
const springboard = "com.apple.springboard";
|
|
3
5
|
const developerPattern =
|
|
4
6
|
/(?:Apple Development|iPhone Developer): .+?\([A-Z0-9]{10}\)/;
|
|
5
|
-
const tapTimeoutMs = 3_000;
|
|
6
7
|
|
|
7
|
-
const sheet =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
const sheet = (timeoutMs) =>
|
|
9
|
+
accessibility.find(
|
|
10
|
+
{ app: springboard, role: "sheet", label: /./ },
|
|
11
|
+
{ timeoutMs, poll: true },
|
|
12
|
+
);
|
|
11
13
|
|
|
12
|
-
const
|
|
14
|
+
const inSettings = (selector, timeoutMs) =>
|
|
15
|
+
accessibility.find({ app: settings, ...selector }, { timeoutMs, poll: true });
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
await
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
17
|
+
const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
|
+
|
|
19
|
+
async function tap(element) {
|
|
20
|
+
try {
|
|
21
|
+
await accessibility.tap(element, { timeoutMs: 0 });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.name !== "TimeoutError") {
|
|
24
|
+
throw error;
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
|
-
throw new Error(`the alert stays: ${alert.label}`);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
async function
|
|
28
|
-
const until = Date.now() +
|
|
29
|
+
async function dismiss(alert, index) {
|
|
30
|
+
const until = Date.now() + 10_000;
|
|
31
|
+
let absent = 0;
|
|
29
32
|
do {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const shown = await sheet();
|
|
34
|
+
if (shown === null) {
|
|
35
|
+
absent += 1;
|
|
36
|
+
if (absent === 2) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (shown.label !== alert.label) {
|
|
34
42
|
return;
|
|
35
43
|
}
|
|
36
|
-
|
|
44
|
+
absent = 0;
|
|
45
|
+
await tap(shown.findAll({ role: "button" })[index]);
|
|
37
46
|
} while (Date.now() < until);
|
|
47
|
+
throw new Error(`the alert stays: ${alert.label}`);
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
async function
|
|
50
|
+
async function answerPrompt() {
|
|
51
|
+
const alert = await sheet();
|
|
52
|
+
const buttons = alert?.findAll({ role: "button" }) ?? [];
|
|
53
|
+
if (alert?.label?.includes(name) && [2, 3].includes(buttons.length)) {
|
|
54
|
+
console.debug("answering a permission alert");
|
|
55
|
+
// The location alert shows an icon and lists Allow Once first; the cellular
|
|
56
|
+
// data alert of China mainland devices shows none and lists Wi-Fi and
|
|
57
|
+
// cellular first (iPhone18,2, iOS 27.2 24B115).
|
|
58
|
+
const location = alert.findAll({ role: "image" }).length > 0;
|
|
59
|
+
await dismiss(alert, buttons.length === 2 || location ? 1 : 0);
|
|
60
|
+
}
|
|
61
|
+
return alert !== null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function launch() {
|
|
41
65
|
try {
|
|
66
|
+
console.debug(`launching ${name}`);
|
|
42
67
|
await device.launchApplication(id, { restart: true });
|
|
43
|
-
await allowPrompts(name, 3_000);
|
|
44
68
|
return null;
|
|
45
69
|
} catch (error) {
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
const until = Date.now() + 5_000;
|
|
71
|
+
do {
|
|
72
|
+
const alert = await sheet(Math.max(until - Date.now(), 0));
|
|
48
73
|
const buttons = alert?.findAll({ role: "button" }) ?? [];
|
|
49
74
|
if (buttons.length === 1) {
|
|
50
75
|
const developer = alert
|
|
51
76
|
.findAll({ role: "text" })
|
|
52
77
|
.map((text) => developerPattern.exec(text.label ?? "")?.[0])
|
|
53
78
|
.find((found) => found !== undefined);
|
|
54
|
-
await dismiss(alert,
|
|
79
|
+
await dismiss(alert, 0);
|
|
55
80
|
if (developer === undefined) {
|
|
56
81
|
throw new Error(`the alert names no developer: ${alert.label}`);
|
|
57
82
|
}
|
|
58
83
|
return developer;
|
|
59
84
|
}
|
|
60
|
-
|
|
61
|
-
}
|
|
85
|
+
} while (Date.now() < until);
|
|
62
86
|
throw error;
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{ app: settings, ...selector },
|
|
70
|
-
{ timeoutMs: 1_500, poll: true },
|
|
71
|
-
);
|
|
72
|
-
if (found !== null && !found.partial && !found.offScreen) {
|
|
73
|
-
return found;
|
|
74
|
-
}
|
|
75
|
-
const distance = found === null ? 0.5 : 0.25;
|
|
76
|
-
await input.swipe(
|
|
77
|
-
[bounds.width * 0.6, bounds.height * (0.3 + distance)],
|
|
78
|
-
[bounds.width * 0.6, bounds.height * 0.3],
|
|
79
|
-
{ durationMs: 300 },
|
|
80
|
-
);
|
|
81
|
-
await sleep(500);
|
|
90
|
+
function developerRows(list, developer) {
|
|
91
|
+
if (list.findAll({ role: "button", identifier: "VPN" }).length === 0) {
|
|
92
|
+
return [];
|
|
82
93
|
}
|
|
83
|
-
|
|
94
|
+
const elsewhere = new Set(
|
|
95
|
+
list
|
|
96
|
+
.findAll({
|
|
97
|
+
role: "button",
|
|
98
|
+
within: {
|
|
99
|
+
identifier: "com.apple.settings.sidebar.collectionView",
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
.concat(
|
|
103
|
+
list.findAll({
|
|
104
|
+
role: "button",
|
|
105
|
+
within: { role: /^(navigationBar|searchField)$/ },
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
.map((row) => row.identifier),
|
|
109
|
+
);
|
|
110
|
+
return list
|
|
111
|
+
.findAll({ role: "button" })
|
|
112
|
+
.filter(
|
|
113
|
+
(row) =>
|
|
114
|
+
row.identifier !== undefined &&
|
|
115
|
+
row.identifier !== "BackButton" &&
|
|
116
|
+
row.identifier !== "VPN" &&
|
|
117
|
+
!row.identifier.startsWith("com.apple.settings.") &&
|
|
118
|
+
row.identifier !== row.label &&
|
|
119
|
+
!elsewhere.has(row.identifier),
|
|
120
|
+
)
|
|
121
|
+
.sort(
|
|
122
|
+
(a, b) =>
|
|
123
|
+
developer.includes(b.identifier) - developer.includes(a.identifier),
|
|
124
|
+
);
|
|
84
125
|
}
|
|
85
126
|
|
|
86
|
-
async function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await input.pressHome();
|
|
97
|
-
await sleep(800);
|
|
98
|
-
await device.launchApplication(settings);
|
|
99
|
-
await sleep(1_000);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
for (let wait = 0; wait < 10; wait++) {
|
|
103
|
-
await sleep(300);
|
|
104
|
-
if (loaded(await accessibility.snapshot({ scope: "frontApp" }))) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
127
|
+
async function openDeviceManagement(developer) {
|
|
128
|
+
await device.launchApplication(settings, {
|
|
129
|
+
url: "prefs:root=General&path=ManagedConfigurationList",
|
|
130
|
+
});
|
|
131
|
+
const until = Date.now() + 10_000;
|
|
132
|
+
do {
|
|
133
|
+
const list = await accessibility.snapshot({ scope: "frontApp" });
|
|
134
|
+
const rows = developerRows(list, developer);
|
|
135
|
+
if (rows.length > 0) {
|
|
136
|
+
return rows;
|
|
107
137
|
}
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
await answerPrompt();
|
|
139
|
+
} while (Date.now() < until);
|
|
140
|
+
throw new Error(`Settings lists no app from ${developer}`);
|
|
110
141
|
}
|
|
111
142
|
|
|
112
|
-
async function
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
async function allowDeveloper(row) {
|
|
144
|
+
const control = {
|
|
145
|
+
role: "text",
|
|
146
|
+
label: new RegExp(
|
|
147
|
+
`.${escaped(row.identifier)}|${escaped(row.identifier)}.`,
|
|
148
|
+
),
|
|
149
|
+
};
|
|
150
|
+
if ((await inSettings(control, 1_500)) === null) {
|
|
151
|
+
return false;
|
|
119
152
|
}
|
|
120
|
-
|
|
121
|
-
|
|
153
|
+
const until = Date.now() + 5_000;
|
|
154
|
+
do {
|
|
155
|
+
const shown = await inSettings(control);
|
|
156
|
+
if (shown !== null) {
|
|
157
|
+
await input.tap(shown.point);
|
|
158
|
+
}
|
|
159
|
+
const alert = await sheet();
|
|
160
|
+
const buttons = alert?.findAll({ role: "button" }) ?? [];
|
|
161
|
+
if (buttons.length === 2) {
|
|
162
|
+
await dismiss(alert, 0);
|
|
163
|
+
while (
|
|
164
|
+
(await inSettings(control)) !== null &&
|
|
165
|
+
Date.now() < until + 10_000
|
|
166
|
+
);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
} while (Date.now() < until);
|
|
170
|
+
throw new Error("Settings did not ask to allow the developer");
|
|
122
171
|
}
|
|
123
172
|
|
|
124
|
-
async function trust(developer
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
);
|
|
139
|
-
const candidates = (list) => {
|
|
140
|
-
const elsewhere = new Set(
|
|
141
|
-
list
|
|
142
|
-
.findAll({
|
|
143
|
-
role: "button",
|
|
144
|
-
within: {
|
|
145
|
-
identifier: "com.apple.settings.sidebar.collectionView",
|
|
146
|
-
},
|
|
147
|
-
})
|
|
148
|
-
.concat(
|
|
149
|
-
list.findAll({
|
|
150
|
-
role: "button",
|
|
151
|
-
within: { role: /^(navigationBar|searchField)$/ },
|
|
152
|
-
}),
|
|
153
|
-
)
|
|
154
|
-
.map((row) => row.identifier),
|
|
155
|
-
);
|
|
156
|
-
return list
|
|
157
|
-
.findAll({ role: "button" })
|
|
158
|
-
.filter(
|
|
159
|
-
(row) =>
|
|
160
|
-
row.identifier !== undefined &&
|
|
161
|
-
row.identifier !== "BackButton" &&
|
|
162
|
-
row.identifier !== "VPN" &&
|
|
163
|
-
row.identifier !== row.label &&
|
|
164
|
-
!elsewhere.has(row.identifier),
|
|
165
|
-
)
|
|
166
|
-
.map((row) => row.identifier);
|
|
167
|
-
};
|
|
168
|
-
let rows = [];
|
|
169
|
-
for (let attempt = 0; attempt < 15 && rows.length === 0; attempt++) {
|
|
170
|
-
await sleep(400);
|
|
171
|
-
rows = candidates(await accessibility.snapshot({ scope: "frontApp" }));
|
|
172
|
-
}
|
|
173
|
-
for (const row of rows) {
|
|
174
|
-
const button = await accessibility.find(
|
|
175
|
-
{ app: settings, role: "button", identifier: row },
|
|
176
|
-
{ timeoutMs: 3_000, poll: true },
|
|
177
|
-
);
|
|
173
|
+
async function trust(developer) {
|
|
174
|
+
console.debug(`trusting ${developer} in Settings`);
|
|
175
|
+
const page = { label: new RegExp(escaped(developer)) };
|
|
176
|
+
for (const row of await openDeviceManagement(developer)) {
|
|
177
|
+
const until = Date.now() + 3_000;
|
|
178
|
+
let opened = null;
|
|
179
|
+
do {
|
|
180
|
+
await tap(row);
|
|
181
|
+
opened = await inSettings(page);
|
|
182
|
+
} while (opened === null && Date.now() < until);
|
|
183
|
+
if (opened !== null && (await allowDeveloper(row))) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const button = await inSettings({ identifier: "BackButton" }, 3_000);
|
|
178
187
|
if (button === null) {
|
|
179
|
-
|
|
188
|
+
throw new Error("Settings shows no back button");
|
|
180
189
|
}
|
|
181
|
-
await
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
await tap(button);
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`Settings lists no app from ${developer}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function waitForUnlock() {
|
|
196
|
+
let announced = false;
|
|
197
|
+
for (;;) {
|
|
198
|
+
try {
|
|
199
|
+
if (!(await device.locked.get())) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error.name !== "UnavailableError") {
|
|
204
|
+
throw error;
|
|
194
205
|
}
|
|
195
206
|
}
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
if (!announced) {
|
|
208
|
+
console.debug("unlock the device to continue");
|
|
209
|
+
announced = true;
|
|
199
210
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function finishSetup() {
|
|
215
|
+
const until = Date.now() + 60_000;
|
|
216
|
+
do {
|
|
217
|
+
if (!(await answerPrompt())) {
|
|
218
|
+
const finished = await accessibility.find({
|
|
219
|
+
app: id,
|
|
220
|
+
identifier: "setup-finished",
|
|
221
|
+
});
|
|
222
|
+
if (finished !== null) {
|
|
223
|
+
console.log(`${name} is running`);
|
|
206
224
|
return;
|
|
207
225
|
}
|
|
208
|
-
await sleep(300);
|
|
209
226
|
}
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
throw new Error(`Settings lists no app from ${developer}`);
|
|
227
|
+
} while (Date.now() < until);
|
|
228
|
+
throw new Error(`${name} did not finish setting up`);
|
|
213
229
|
}
|
|
214
230
|
|
|
215
231
|
async function main() {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
(await device.installedApplications()).find((app) => app.bundleId === id)
|
|
219
|
-
?.name ?? "Swipt";
|
|
220
|
-
await allowPrompts(name, 0);
|
|
221
|
-
const developer = await launch(name);
|
|
222
|
-
if (developer === null) {
|
|
223
|
-
console.log(`${name} is running`);
|
|
232
|
+
if (mode === "prompts") {
|
|
233
|
+
await finishSetup();
|
|
224
234
|
return;
|
|
225
235
|
}
|
|
226
|
-
|
|
227
|
-
await
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
236
|
+
console.debug("unlocking the device");
|
|
237
|
+
await device.unlock().catch(() => {});
|
|
238
|
+
if (waitsForUnlock === "wait") {
|
|
239
|
+
await waitForUnlock();
|
|
240
|
+
}
|
|
241
|
+
await answerPrompt();
|
|
242
|
+
const developer = await launch();
|
|
243
|
+
if (developer !== null) {
|
|
244
|
+
await trust(developer);
|
|
245
|
+
console.log(`${developer} is trusted`);
|
|
231
246
|
}
|
|
232
|
-
console.log(`${name} is running; ${developer} is trusted`);
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
await main();
|