@xenon-device-management/xenon 2.12.0 → 2.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/LICENSE +15 -0
- package/README.md +223 -488
- package/lib/package.json +2 -2
- package/lib/public/assets/{AnnotationOverlay-Crkn72_3.js → AnnotationOverlay-Cvpkk4r9.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BThVlj_j.js → ApiKeyGate-Bhn5ZtVM.js} +1 -1
- package/lib/public/assets/{BugReportButton-Dwa0GnBh.js → BugReportButton-BA6aEjTC.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-Ccc-pOls.js → DeviceMosaicView-ChhHnpFQ.js} +1 -1
- package/lib/public/assets/{EmptyState-BeuTrlN_.js → EmptyState-CgzbuUvO.js} +1 -1
- package/lib/public/assets/{FieldGroup-DBIaNxl1.js → FieldGroup-BHc_5qNc.js} +1 -1
- package/lib/public/assets/{FilterMenu-BgSAS6iT.js → FilterMenu-D7OMGuRc.js} +1 -1
- package/lib/public/assets/{Menu-GCbP6gYN.js → Menu-C173JJ0l.js} +1 -1
- package/lib/public/assets/{Modal-ByESeBRC.js → Modal-ChvVUI3B.js} +1 -1
- package/lib/public/assets/{RecordingPage-Bwyf3WMl.js → RecordingPage-Dhw4d_O6.js} +1 -1
- package/lib/public/assets/{RecordingsPage-7kM1rB3i.js → RecordingsPage-z97ftGK-.js} +2 -2
- package/lib/public/assets/{SegmentedControl-B8tTU8kD.js → SegmentedControl-DbXhBGZC.js} +1 -1
- package/lib/public/assets/{SettingCard-Clg_Ijbc.js → SettingCard-C908GgiI.js} +1 -1
- package/lib/public/assets/{Table-CIaR0QWV.js → Table-B1DB_ZN-.js} +1 -1
- package/lib/public/assets/{activity-Bengh9SD.js → activity-Css0xNvp.js} +1 -1
- package/lib/public/assets/ai-settings-BMQK3-yN.js +21 -0
- package/lib/public/assets/{api-keys-CrGlbfn-.js → api-keys-CbJ42eDQ.js} +1 -1
- package/lib/public/assets/{apps-DIbcGVlU.js → apps-DW1pw2re.js} +2 -2
- package/lib/public/assets/{arrow-left-BtJIXXMY.js → arrow-left-Dpog9L0F.js} +1 -1
- package/lib/public/assets/{arrow-right-zm-NagQh.js → arrow-right-CsBIlSIw.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DdU8YEYp.js → arrow-up-right-z1tRQE4R.js} +1 -1
- package/lib/public/assets/{auth-shell-CHmBFmOz.js → auth-shell-mXiX5vNk.js} +2 -2
- package/lib/public/assets/{builds-page-JkzWKRpU.js → builds-page-BcFn23bE.js} +1 -1
- package/lib/public/assets/{button-CqhPBGRj.js → button-Cq_zHETv.js} +1 -1
- package/lib/public/assets/{calendar-C7eYYJTo.js → calendar-CKxABX9q.js} +1 -1
- package/lib/public/assets/{check-DpbIM4E0.js → check-C7xu-hGU.js} +1 -1
- package/lib/public/assets/{chevron-right-Dl4X1PRz.js → chevron-right-BiqfdCGA.js} +1 -1
- package/lib/public/assets/{circle-check-Du3sjbfV.js → circle-check-CyBjjPxR.js} +1 -1
- package/lib/public/assets/{circle-x-BSSqhpXi.js → circle-x-6gMRcx6z.js} +1 -1
- package/lib/public/assets/{clock-DMT60v1C.js → clock-U1rDTT1h.js} +1 -1
- package/lib/public/assets/{copy-BKNyOehd.js → copy-BIM0ZX0L.js} +1 -1
- package/lib/public/assets/device-explorer-DSpAA_t9.js +279 -0
- package/lib/public/assets/{download-BM6Xn22t.js → download-DDBRXnAZ.js} +1 -1
- package/lib/public/assets/{forgot-password-B8WqMqBT.js → forgot-password-Dpg7yxM8.js} +1 -1
- package/lib/public/assets/{index-ClrpAMAT.js → index-BGBqohfQ.js} +43 -43
- package/lib/public/assets/{index-DauQh6ie.js → index-BQ2luT7L.js} +1 -1
- package/lib/public/assets/{input-CyKdLnEx.js → input-BvB1cjHE.js} +1 -1
- package/lib/public/assets/{line-chart-EIBXwYGo.js → line-chart-m78VggnW.js} +1 -1
- package/lib/public/assets/{list-checks-fWsgD9bI.js → list-checks-cY7o1QTL.js} +1 -1
- package/lib/public/assets/{lock-CVCe56TH.js → lock-loz8txQm.js} +1 -1
- package/lib/public/assets/{login-BJ8a7yVD.js → login-BYOvz4lx.js} +1 -1
- package/lib/public/assets/maintenance-settings-BxbA7SkB.js +6 -0
- package/lib/public/assets/{monitor-Bw1YQZnL.js → monitor-C4HPHb6s.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-C01jbqkO.js → mouse-pointer-2-BkdKtCzi.js} +1 -1
- package/lib/public/assets/{network-CBGUjJDJ.js → network-C5e0MIWo.js} +1 -1
- package/lib/public/assets/{overview-Cxe8aQ7C.js → overview-tlu8ijGd.js} +1 -1
- package/lib/public/assets/{page-header-B92DKLiq.js → page-header-FXPlo6az.js} +1 -1
- package/lib/public/assets/{play-Ck0L-0_m.js → play-CWdk0_Sy.js} +1 -1
- package/lib/public/assets/{plus-Dq3tCy2N.js → plus-B2xCnntV.js} +1 -1
- package/lib/public/assets/{profile-page--dkDiKbr.js → profile-page-D080qzr4.js} +1 -1
- package/lib/public/assets/{recording-group-store-5BYIFFN9.js → recording-group-store-B75435xR.js} +1 -1
- package/lib/public/assets/{reset-password-KYWlfjia.js → reset-password-D3u4Mbvn.js} +1 -1
- package/lib/public/assets/{runbook-page-mnsxgS1d.js → runbook-page-hPRZMy7s.js} +1 -1
- package/lib/public/assets/{select-BBOIZTYm.js → select-12RThAXc.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-CcI2j_Su.js → selector-detail-redirect-uQwvV7EQ.js} +1 -1
- package/lib/public/assets/{selector-health-page-BdJb_x5L.js → selector-health-page-CThoxDFO.js} +2 -2
- package/lib/public/assets/{session-detail-page-BjLaJy8z.js → session-detail-page-Bc02YjMm.js} +1 -1
- package/lib/public/assets/settings-C1VIluuT.js +6 -0
- package/lib/public/assets/{stat-tile-BU9e4s36.js → stat-tile-B02WEte8.js} +1 -1
- package/lib/public/assets/{tablet-hgbEwrWq.js → tablet-Dj30VGZl.js} +1 -1
- package/lib/public/assets/{teams-uiiG4hZM.js → teams-uL4fjTDR.js} +1 -1
- package/lib/public/assets/{trash-2-NK_Iazmg.js → trash-2-B54e-TfY.js} +1 -1
- package/lib/public/assets/{upload-BUX8TNFi.js → upload-B3k8WCSP.js} +1 -1
- package/lib/public/assets/{use-builds-data-DZszmFyl.js → use-builds-data-BFn90NH3.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-BK4B9kRC.js → use-password-reset-mode-DL2TmWZ0.js} +1 -1
- package/lib/public/assets/{users-BU4XBbMT.js → users-CqBuxMlX.js} +1 -1
- package/lib/public/assets/{users-BDh1xjad.js → users-DjQSucKb.js} +1 -1
- package/lib/public/assets/{video-off-BOjNwT4R.js → video-off-T0rlt0GA.js} +1 -1
- package/lib/public/assets/webhook-settings-DAHgnJyZ.js +1 -0
- package/lib/public/assets/{zap-DTWwVMUg.js → zap-gBHqutZV.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/app/apiErrors.js +118 -0
- package/lib/src/app/index.js +6 -1
- package/lib/src/app/openapi/control.yaml +3129 -0
- package/lib/src/app/openapi/grid.yaml +2295 -0
- package/lib/src/app/openapi/identity.yaml +2168 -0
- package/lib/src/app/openapi/platform.yaml +2885 -0
- package/lib/src/app/openapi/sessions.yaml +3784 -0
- package/lib/src/app/routers/bug-report.js +4 -1
- package/lib/src/app/routers/config.js +6 -107
- package/lib/src/app/routers/control.js +117 -59
- package/lib/src/app/routers/dashboard.js +13 -7
- package/lib/src/app/routers/grid.js +54 -14
- package/lib/src/app/routers/profile.js +27 -13
- package/lib/src/app/routers/recordings.js +11 -5
- package/lib/src/app/routers/reservation.js +63 -15
- package/lib/src/app/routers/users.js +4 -0
- package/lib/src/app/routers/webhook.js +17 -8
- package/lib/src/app/swagger.js +259 -177
- package/lib/src/data-service/device-service.js +4 -1
- package/lib/src/data-service/deviceFieldOwners.js +1 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +5 -2
- package/lib/src/device-managers/ios/WDAClient.js +153 -41
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +38 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +2 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/middleware/csrfMiddleware.js +13 -5
- package/lib/src/middleware/rateLimitMiddleware.js +19 -5
- package/lib/src/middleware/roleGuard.js +20 -0
- package/lib/src/services/AIService.js +13 -3
- package/lib/src/services/NotificationService.js +28 -28
- package/lib/src/services/bug-report/BugReportService.js +8 -2
- package/lib/src/services/lease/LeaseService.js +71 -20
- package/lib/src/services/omni-vision/OmniVisionService.js +14 -5
- package/lib/src/services/recording/RecordingOrchestrator.js +16 -2
- package/lib/test/helpers/expressRoutes.js +41 -0
- package/lib/test/integration/team-visibility-control.spec.js +2 -2
- package/lib/test/unit/access-scopes.spec.js +227 -0
- package/lib/test/unit/api-error-handling.spec.js +179 -0
- package/lib/test/unit/bug-report/route.spec.js +29 -0
- package/lib/test/unit/bug-report/service.spec.js +30 -0
- package/lib/test/unit/control-honest-answers.spec.js +134 -0
- package/lib/test/unit/device-allocation-routes.spec.js +232 -0
- package/lib/test/unit/healing-state-endpoints.spec.js +8 -5
- package/lib/test/unit/install-repository-app-team.spec.js +4 -1
- package/lib/test/unit/lease/LeaseService.spec.js +5 -4
- package/lib/test/unit/lease/lease-device-match.spec.js +161 -0
- package/lib/test/unit/lease/lease-session-ownership.spec.js +10 -9
- package/lib/test/unit/omni-vision-failures.spec.js +81 -0
- package/lib/test/unit/openapi-coverage.spec.js +114 -0
- package/lib/test/unit/profile-router.test.js +42 -0
- package/lib/test/unit/rateLimitMiddleware.test.js +49 -0
- package/lib/test/unit/recording-orchestrator.spec.js +80 -0
- package/lib/test/unit/recordings-library-routes.spec.js +37 -0
- package/lib/test/unit/reservation-team-visibility.spec.js +4 -3
- package/lib/test/unit/reset-link.test.js +2 -1
- package/lib/test/unit/stream-ticket-identity.spec.js +1 -1
- package/lib/test/unit/users-router.test.js +14 -1
- package/lib/test/unit/wda-client-failures.spec.js +90 -0
- package/lib/test/unit/wda-client-session.spec.js +128 -0
- package/lib/test/unit/wda-clipboard-write.spec.js +135 -0
- package/lib/test/unit/webhook-delivery.spec.js +142 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/prisma/migrations/20261004120000_reservation_holder/migration.sql +2 -0
- package/prisma/schema.prisma +2 -0
- package/scripts/dev/readme-screenshots.js +231 -0
- package/lib/public/assets/ai-settings-G9SymiPH.js +0 -21
- package/lib/public/assets/device-explorer-CoPR8DW3.js +0 -279
- package/lib/public/assets/maintenance-settings-DcRmTLS6.js +0 -6
- package/lib/public/assets/settings-B6cpMhsx.js +0 -6
- package/lib/public/assets/webhook-settings-BMzyUcx5.js +0 -1
- package/lib/src/app/swagger-docs.js +0 -1701
|
@@ -19,7 +19,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
19
19
|
};
|
|
20
20
|
var WDAClient_1;
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.WDAClient = void 0;
|
|
22
|
+
exports.WDAClient = exports.UnsupportedKeyError = void 0;
|
|
23
23
|
const axios_1 = __importDefault(require("axios"));
|
|
24
24
|
const semver_1 = __importDefault(require("semver"));
|
|
25
25
|
const logger_1 = __importDefault(require("../../logger"));
|
|
@@ -36,6 +36,19 @@ const os_1 = __importDefault(require("os"));
|
|
|
36
36
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
37
37
|
const execFilePromise = (0, util_1.promisify)(child_process_1.execFile);
|
|
38
38
|
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
39
|
+
/** WDA's answer for a session that no longer exists: 404 "invalid session id". */
|
|
40
|
+
function isInvalidSession(err) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
return ((_c = (_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.error) === 'invalid session id';
|
|
43
|
+
}
|
|
44
|
+
/** A key name the iPhone has no equivalent for. Device control answers 400. */
|
|
45
|
+
class UnsupportedKeyError extends Error {
|
|
46
|
+
constructor(key) {
|
|
47
|
+
super(`The key "${key}" isn't available on an iPhone.`);
|
|
48
|
+
this.name = 'UnsupportedKeyError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.UnsupportedKeyError = UnsupportedKeyError;
|
|
39
52
|
let WDAClient = WDAClient_1 = class WDAClient {
|
|
40
53
|
constructor() {
|
|
41
54
|
this.log = logger_1.default.scope('WDAClient');
|
|
@@ -71,7 +84,7 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
71
84
|
}
|
|
72
85
|
performWDACommand(udid_1, method_1, endpoint_1, data_1) {
|
|
73
86
|
return __awaiter(this, arguments, void 0, function* (udid, method, endpoint, data, retryCount = 0, options) {
|
|
74
|
-
var _a, _b, _c;
|
|
87
|
+
var _a, _b, _c, _d;
|
|
75
88
|
const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
|
|
76
89
|
if (!device)
|
|
77
90
|
throw new Error(`Device ${udid} not found`);
|
|
@@ -83,12 +96,12 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
83
96
|
throw new Error(`WDA port not available for ${udid}`);
|
|
84
97
|
const cacheKey = `${udid}:${port}`;
|
|
85
98
|
let cached = this.wdaConnectionCache.get(cacheKey);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
// The preview's session wins: each preview creates its own, so after a
|
|
100
|
+
// restart on the same port the cached one is dead.
|
|
101
|
+
const previewSid = typedi_1.Container.get(IOSStreamService_1.default).getWDASessionId(udid);
|
|
102
|
+
if (previewSid && previewSid !== (cached === null || cached === void 0 ? void 0 : cached.sessionId)) {
|
|
103
|
+
cached = { host: '127.0.0.1', pathPrefix: `/session/${previewSid}`, sessionId: previewSid };
|
|
104
|
+
this.wdaConnectionCache.set(cacheKey, cached);
|
|
92
105
|
}
|
|
93
106
|
const prefixes = (cached === null || cached === void 0 ? void 0 : cached.sessionId) ? [`/session/${cached.sessionId}`] : ['', '/session/any'];
|
|
94
107
|
let lastError;
|
|
@@ -98,8 +111,10 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
98
111
|
const targetHost = '127.0.0.1';
|
|
99
112
|
for (const prefix of prefixes) {
|
|
100
113
|
try {
|
|
114
|
+
// WDA serves /wda/homescreen only without a session; inside one it
|
|
115
|
+
// answers 404 "unknown command".
|
|
101
116
|
const isSessionless = !(options === null || options === void 0 ? void 0 : options.useSessionPath) &&
|
|
102
|
-
|
|
117
|
+
['/status', '/health', '/wda/healthcheck', '/wda/homescreen'].includes(endpoint);
|
|
103
118
|
const url = `http://${targetHost}:${port}${isSessionless ? '' : prefix}${endpoint}`;
|
|
104
119
|
// Principal Stability: Screenshots, activation, and script execution need longer timeouts.
|
|
105
120
|
// Heartbeats and Status checks should be snappy to fail-over quickly.
|
|
@@ -139,17 +154,33 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
139
154
|
if (retryCount < 2) {
|
|
140
155
|
// Phase 1: Dead Session Recovery (404)
|
|
141
156
|
if (status === 404 && prefix.includes('/session/')) {
|
|
157
|
+
const deadSession = isInvalidSession(err);
|
|
142
158
|
// Special Intelligence: Some WDA versions don't support /execute, /wda/homescreen, etc.
|
|
143
159
|
// We should NOT clear the session if these specific commands fail with 404,
|
|
144
|
-
// as they might just be unsupported features.
|
|
160
|
+
// as they might just be unsupported features. A dead session is
|
|
161
|
+
// not one of those: it was, for every button after a preview restart.
|
|
145
162
|
const NON_FATAL_ENDPOINTS = ['execute', 'homescreen', 'pressButton'];
|
|
146
|
-
if (NON_FATAL_ENDPOINTS.some((e) => endpoint.includes(e))) {
|
|
163
|
+
if (!deadSession && NON_FATAL_ENDPOINTS.some((e) => endpoint.includes(e))) {
|
|
147
164
|
this.log.debug(`[WDA] Command ${endpoint} failed with 404 (Unsupported or Logical Error). Skipping session reset.`);
|
|
148
165
|
throw err;
|
|
149
166
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
typedi_1.Container.get(IOSStreamService_1.default)
|
|
167
|
+
// WDA names its live session in every answer, a refusal included.
|
|
168
|
+
const liveSid = (_d = err.response.data) === null || _d === void 0 ? void 0 : _d.sessionId;
|
|
169
|
+
const stream = typedi_1.Container.get(IOSStreamService_1.default);
|
|
170
|
+
if (deadSession && typeof liveSid === 'string' && liveSid !== (cached === null || cached === void 0 ? void 0 : cached.sessionId)) {
|
|
171
|
+
this.log.warn(`[WDA] Session ${cached === null || cached === void 0 ? void 0 : cached.sessionId} is gone; WebDriverAgent's is ${liveSid}. Retrying in it...`);
|
|
172
|
+
this.wdaConnectionCache.set(cacheKey, {
|
|
173
|
+
host: targetHost,
|
|
174
|
+
pathPrefix: `/session/${liveSid}`,
|
|
175
|
+
sessionId: liveSid,
|
|
176
|
+
});
|
|
177
|
+
stream.setWDASessionId(udid, liveSid);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.log.warn(`[WDA] Session ${cached === null || cached === void 0 ? void 0 : cached.sessionId} appears dead. Resetting and retrying...`);
|
|
181
|
+
this.wdaConnectionCache.delete(cacheKey);
|
|
182
|
+
stream.setWDASessionId(udid, '');
|
|
183
|
+
}
|
|
153
184
|
return this.performWDACommand(udid, method, endpoint, data, retryCount + 1, options);
|
|
154
185
|
}
|
|
155
186
|
// Phase 2: Transient State Recovery (500)
|
|
@@ -331,13 +362,12 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
331
362
|
yield this.sendWDACommand(udid, 'post', '/wda/pressButton', { name: buttonName });
|
|
332
363
|
}
|
|
333
364
|
catch (e) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
365
|
+
// Fallback for extremely old WDA. A failure is the caller's to see:
|
|
366
|
+
// it used to be swallowed, and control answered 200 for a button
|
|
367
|
+
// that was never pressed.
|
|
368
|
+
if (buttonName !== 'home')
|
|
369
|
+
throw e;
|
|
370
|
+
yield this.sendWDACommand(udid, 'post', '/wda/homescreen', {});
|
|
341
371
|
}
|
|
342
372
|
return;
|
|
343
373
|
}
|
|
@@ -356,17 +386,24 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
356
386
|
}
|
|
357
387
|
catch (e) {
|
|
358
388
|
this.log.debug(`[WDA] Special key '${n}' failed via /wda/keys: ${e.message}`);
|
|
359
|
-
// Fallback to /wda/type if keys fails
|
|
360
|
-
yield this.sendWDACommand(udid, 'post', '/wda/type', { text: keyboardKeys[n] })
|
|
389
|
+
// Fallback to /wda/type if keys fails; if that fails too, say so.
|
|
390
|
+
yield this.sendWDACommand(udid, 'post', '/wda/type', { text: keyboardKeys[n] });
|
|
361
391
|
}
|
|
362
392
|
return;
|
|
363
393
|
}
|
|
364
|
-
//
|
|
394
|
+
// Anything else may be a button WDA knows by name. If WDA answered and
|
|
395
|
+
// refused it, the iPhone has no such key: say so rather than answer as if
|
|
396
|
+
// it was pressed. If WDA couldn't be reached, that is the failure.
|
|
365
397
|
try {
|
|
366
398
|
yield this.sendWDACommand(udid, 'post', '/wda/pressButton', { name: n });
|
|
367
399
|
}
|
|
368
400
|
catch (e) {
|
|
369
401
|
this.log.debug(`[WDA] Generic pressButton failed for '${n}': ${e.message}`);
|
|
402
|
+
// A 404 is a missing session or route, never a missing button: WDA
|
|
403
|
+
// refuses a button it doesn't know with a 500.
|
|
404
|
+
if ((e === null || e === void 0 ? void 0 : e.response) && e.response.status !== 404)
|
|
405
|
+
throw new UnsupportedKeyError(String(key));
|
|
406
|
+
throw e;
|
|
370
407
|
}
|
|
371
408
|
});
|
|
372
409
|
}
|
|
@@ -577,37 +614,112 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
577
614
|
}
|
|
578
615
|
return value;
|
|
579
616
|
}
|
|
617
|
+
// These three used to catch WDA's error and log it at debug level, so
|
|
618
|
+
// device control answered 200 for a clipboard write, lock or unlock that
|
|
619
|
+
// never happened. The error now reaches the route, which answers 500.
|
|
620
|
+
//
|
|
621
|
+
// On an iPhone, iOS ignores a pasteboard write from an app in the
|
|
622
|
+
// background and WDA still answers success, so the write is made with WDA's
|
|
623
|
+
// own app in front and read back before it counts.
|
|
580
624
|
setClipboard(udid, content) {
|
|
581
625
|
return __awaiter(this, void 0, void 0, function* () {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
626
|
+
const encoded = Buffer.from(content).toString('base64');
|
|
627
|
+
const write = () => this.sendWDACommand(udid, 'post', '/wda/setPasteboard', {
|
|
628
|
+
content: encoded,
|
|
629
|
+
contentType: 'plaintext',
|
|
630
|
+
});
|
|
631
|
+
if (!(yield this.isRealDevice(udid))) {
|
|
632
|
+
yield write();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
yield this.withWDAInFront(udid, () => __awaiter(this, void 0, void 0, function* () {
|
|
636
|
+
var _a;
|
|
637
|
+
yield write();
|
|
638
|
+
const res = yield this.sendWDACommand(udid, 'post', '/wda/getPasteboard', {
|
|
585
639
|
contentType: 'plaintext',
|
|
586
640
|
});
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
641
|
+
const readBack = (_a = res.data) === null || _a === void 0 ? void 0 : _a.value;
|
|
642
|
+
if (readBack !== encoded && this.parsePasteboardResponse(res.data) !== content.trim()) {
|
|
643
|
+
throw new Error('The iPhone didn’t take the clipboard text.');
|
|
644
|
+
}
|
|
645
|
+
}));
|
|
591
646
|
});
|
|
592
647
|
}
|
|
593
|
-
|
|
648
|
+
isRealDevice(udid) {
|
|
649
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
650
|
+
const device = yield device_store_1.DeviceStoreFactory.getStore()
|
|
651
|
+
.findDevice({ udid })
|
|
652
|
+
.catch(() => undefined);
|
|
653
|
+
return !!(device === null || device === void 0 ? void 0 : device.realDevice);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
activeBundleId(udid) {
|
|
657
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
658
|
+
var _a, _b, _c;
|
|
659
|
+
const res = yield this.sendWDACommand(udid, 'get', '/wda/activeAppInfo');
|
|
660
|
+
return ((_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.bundleId) || ((_c = res.data) === null || _c === void 0 ? void 0 : _c.bundleId);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Runs `action` with WDA's own app in front, then puts back the app that
|
|
665
|
+
* was: the home screen, or the app by its bundle id.
|
|
666
|
+
*
|
|
667
|
+
* Bringing WDA's app forward waits for it to go idle, which it never does:
|
|
668
|
+
* about 21 s on an iPhone 14 Plus, against 0.1 s with the wait off. The wait
|
|
669
|
+
* is a WDA setting shared with any Appium session on the phone, so it is
|
|
670
|
+
* turned off only when its value could be read, and always put back.
|
|
671
|
+
*/
|
|
672
|
+
withWDAInFront(udid, action) {
|
|
594
673
|
return __awaiter(this, void 0, void 0, function* () {
|
|
674
|
+
const previous = yield this.activeBundleId(udid).catch(() => undefined);
|
|
675
|
+
const wdaBundleId = (yield typedi_1.Container.get(IOSStreamService_1.default).detectWDABundleId(udid)) ||
|
|
676
|
+
'com.qasecret.WebDriverAgentRunner.xctrunner';
|
|
677
|
+
const idleWait = yield this.sendWDACommand(udid, 'get', '/appium/settings')
|
|
678
|
+
.then((res) => { var _a, _b; return (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.waitForIdleTimeout; })
|
|
679
|
+
.catch(() => undefined);
|
|
680
|
+
const setIdleWait = (seconds) => this.sendWDACommand(udid, 'post', '/appium/settings', {
|
|
681
|
+
settings: { waitForIdleTimeout: seconds },
|
|
682
|
+
});
|
|
595
683
|
try {
|
|
596
|
-
|
|
684
|
+
if (typeof idleWait === 'number')
|
|
685
|
+
yield setIdleWait(0);
|
|
686
|
+
yield this.sendWDACommand(udid, 'post', '/wda/apps/activate', { bundleId: wdaBundleId });
|
|
687
|
+
// Activation answers before the app is in front, and a write made
|
|
688
|
+
// before then is lost.
|
|
689
|
+
const deadline = Date.now() + 3000;
|
|
690
|
+
while (Date.now() < deadline) {
|
|
691
|
+
if ((yield this.activeBundleId(udid).catch(() => undefined)) === wdaBundleId)
|
|
692
|
+
break;
|
|
693
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
694
|
+
}
|
|
695
|
+
yield action();
|
|
597
696
|
}
|
|
598
|
-
|
|
599
|
-
|
|
697
|
+
finally {
|
|
698
|
+
try {
|
|
699
|
+
if (previous === 'com.apple.springboard') {
|
|
700
|
+
yield this.sendWDACommand(udid, 'post', '/wda/homescreen', {});
|
|
701
|
+
}
|
|
702
|
+
else if (previous && previous !== wdaBundleId) {
|
|
703
|
+
yield this.sendWDACommand(udid, 'post', '/wda/apps/activate', { bundleId: previous });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch (e) {
|
|
707
|
+
this.log.warn(`[Clipboard] Couldn't put back ${previous} on ${udid}: ${e.message}`);
|
|
708
|
+
}
|
|
709
|
+
if (typeof idleWait === 'number') {
|
|
710
|
+
yield setIdleWait(idleWait).catch((e) => this.log.warn(`[Clipboard] Couldn't restore WDA's idle wait on ${udid}: ${e.message}`));
|
|
711
|
+
}
|
|
600
712
|
}
|
|
601
713
|
});
|
|
602
714
|
}
|
|
715
|
+
lock(udid) {
|
|
716
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
717
|
+
yield this.sendWDACommand(udid, 'post', '/wda/lock', {});
|
|
718
|
+
});
|
|
719
|
+
}
|
|
603
720
|
unlock(udid) {
|
|
604
721
|
return __awaiter(this, void 0, void 0, function* () {
|
|
605
|
-
|
|
606
|
-
yield this.sendWDACommand(udid, 'post', '/wda/unlock', {});
|
|
607
|
-
}
|
|
608
|
-
catch (e) {
|
|
609
|
-
this.log.debug(`Failed to unlock device ${udid}: ${e.message}\n${e.stack}`);
|
|
610
|
-
}
|
|
722
|
+
yield this.sendWDACommand(udid, 'post', '/wda/unlock', {});
|
|
611
723
|
});
|
|
612
724
|
}
|
|
613
725
|
/**
|