@xenon-device-management/xenon 2.9.0 → 2.9.2
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/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-DhRFPZ9-.js → AnnotationOverlay-B87n4pUu.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BmZy1Srn.js → ApiKeyGate-CNZJH6Cs.js} +1 -1
- package/lib/public/assets/{BugReportButton-ByLSqwSk.js → BugReportButton-DQ-R87ZV.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-DVfXSoXH.js → DeviceMosaicView-dX3e7O-w.js} +1 -1
- package/lib/public/assets/{EmptyState-Bib6wal8.js → EmptyState-D3fG2GHY.js} +1 -1
- package/lib/public/assets/{FieldGroup-D5sTaKGR.js → FieldGroup-CUmbJZxI.js} +1 -1
- package/lib/public/assets/{FilterMenu-D3XXKTwF.js → FilterMenu-Dm2c-UCk.js} +1 -1
- package/lib/public/assets/{Menu-BiZhr0F7.js → Menu-Dl4uXMkH.js} +1 -1
- package/lib/public/assets/{Modal-BxGNa3Zp.js → Modal-DNmn2RDs.js} +1 -1
- package/lib/public/assets/{RecordingPage-Czml77Wb.js → RecordingPage-Dn3By34o.js} +1 -1
- package/lib/public/assets/{RecordingsPage-Di7nyHqE.js → RecordingsPage-lCUIM4pm.js} +1 -1
- package/lib/public/assets/{SegmentedControl-XEKvCRwO.js → SegmentedControl-BdA_mHiZ.js} +1 -1
- package/lib/public/assets/{SettingCard-YIpOQAjO.js → SettingCard-Dx3WhK7w.js} +1 -1
- package/lib/public/assets/{Table-CqwX6v-1.js → Table-D_2lhvRM.js} +1 -1
- package/lib/public/assets/{activity-Cur7Syd6.js → activity-cNHHOKx5.js} +1 -1
- package/lib/public/assets/{ai-settings-B1OGdSKu.js → ai-settings-t6RMrDkg.js} +1 -1
- package/lib/public/assets/{api-keys-abTul2Oa.js → api-keys-CK_qyQvW.js} +1 -1
- package/lib/public/assets/{apps-yQL1MLfG.js → apps-DCiI3UOb.js} +1 -1
- package/lib/public/assets/{arrow-left-D0ipNN6o.js → arrow-left-CnYs8HVw.js} +1 -1
- package/lib/public/assets/{arrow-right-DwVz_Fly.js → arrow-right-Cc9qm6UG.js} +1 -1
- package/lib/public/assets/{arrow-up-right-xKuKJfZf.js → arrow-up-right-e7yBOb-c.js} +1 -1
- package/lib/public/assets/{auth-shell-00zHLFCk.js → auth-shell-DKrtcIjG.js} +2 -2
- package/lib/public/assets/{builds-page-D4RJAbOZ.js → builds-page-CRd5oEz6.js} +1 -1
- package/lib/public/assets/{button-Cic7S8s2.js → button-C0caCjJB.js} +1 -1
- package/lib/public/assets/{calendar-DyNZ2bq9.js → calendar-BqOOkln_.js} +1 -1
- package/lib/public/assets/{check-CXGZdojb.js → check-DYSXRG79.js} +1 -1
- package/lib/public/assets/{chevron-left-BcNiIt1C.js → chevron-left-BSG6mcpo.js} +1 -1
- package/lib/public/assets/{chevron-right-BxDjkb1I.js → chevron-right-hCNu0CA-.js} +1 -1
- package/lib/public/assets/{circle-check-9isFAWQ6.js → circle-check-DnQ263fa.js} +1 -1
- package/lib/public/assets/{circle-x-CfnPCFLK.js → circle-x-BaRKZyz9.js} +1 -1
- package/lib/public/assets/{clock-Dc4TvnFF.js → clock-XP3iBAhg.js} +1 -1
- package/lib/public/assets/{copy-TFQpalJ2.js → copy-D8d9UzWE.js} +1 -1
- package/lib/public/assets/{device-explorer-CB4bSJtl.js → device-explorer-pdM3L9TC.js} +1 -1
- package/lib/public/assets/{download-1_bq_Ikd.js → download-SaN9-umX.js} +1 -1
- package/lib/public/assets/{forgot-password-B92TZ1GF.js → forgot-password-DfUhEZW5.js} +1 -1
- package/lib/public/assets/{funnel-DC7DsjHe.js → funnel-OefHE-54.js} +1 -1
- package/lib/public/assets/{index-CC2N_uMU.js → index-CzaIyL9t.js} +2 -2
- package/lib/public/assets/{index-BSsnx09Q.js → index-IefGCFQQ.js} +1 -1
- package/lib/public/assets/{input-D2DBpxK2.js → input-BqFzAQT3.js} +1 -1
- package/lib/public/assets/{keycodes-BQuIOMw0.js → keycodes-DDV2OlpQ.js} +1 -1
- package/lib/public/assets/{layers-DxtoRlvi.js → layers-a3GNz2DA.js} +1 -1
- package/lib/public/assets/{list-checks-jQ7YUgdE.js → list-checks-DMrMAFDt.js} +1 -1
- package/lib/public/assets/{lock-WHSsj2dj.js → lock-Eye1CG8p.js} +1 -1
- package/lib/public/assets/{login-BB_xyUWw.js → login-Co1IXuen.js} +1 -1
- package/lib/public/assets/{maintenance-settings-Dwadp8IQ.js → maintenance-settings-D1fw6iqm.js} +1 -1
- package/lib/public/assets/{monitor-DIWQjUlh.js → monitor-U5Y-PxW5.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-D11Re9Gj.js → mouse-pointer-2-CDAwphUm.js} +1 -1
- package/lib/public/assets/{network-BYBgSBjv.js → network-BP_U4iDZ.js} +1 -1
- package/lib/public/assets/{overview-BISAwoyi.js → overview-CsPyPv10.js} +1 -1
- package/lib/public/assets/{page-header-ChX4YiMP.js → page-header-ZdzNEJp9.js} +1 -1
- package/lib/public/assets/{plus-BWNj1XSQ.js → plus-1k0sxXyU.js} +1 -1
- package/lib/public/assets/{profile-page-VZD3gRjV.js → profile-page-B_GWKRy3.js} +1 -1
- package/lib/public/assets/{reset-password-Dz9LzFeX.js → reset-password-DizCTkdw.js} +1 -1
- package/lib/public/assets/{runbook-page-Bl2FodZf.js → runbook-page-DWzRiOLZ.js} +1 -1
- package/lib/public/assets/{select-Bs9CQu5Z.js → select-CiRW6v17.js} +1 -1
- package/lib/public/assets/{selector-detail-page-By7ihhbY.js → selector-detail-page-pA913STP.js} +1 -1
- package/lib/public/assets/{selector-health-xiBb8vNz.js → selector-health-CHLX8KqY.js} +1 -1
- package/lib/public/assets/{selector-health-page-Y4CTbOLH.js → selector-health-page-vVkg8Mii.js} +1 -1
- package/lib/public/assets/{session-detail-page-DqeKRP6c.js → session-detail-page-BCD49zDi.js} +1 -1
- package/lib/public/assets/{settings-DDhV_Dnz.js → settings-DhwZoWv7.js} +1 -1
- package/lib/public/assets/{stat-tile-DMBTDbwt.js → stat-tile-BaE76cnK.js} +1 -1
- package/lib/public/assets/{tablet-Cr5bOgng.js → tablet-CPJV3dBq.js} +1 -1
- package/lib/public/assets/{teams-Bm0UQkR6.js → teams-D0IBa2Ov.js} +1 -1
- package/lib/public/assets/{trash-2-Di_lMeiP.js → trash-2-u8MlXUBD.js} +1 -1
- package/lib/public/assets/{upload-RvXDBmCX.js → upload-Cq8L3kpw.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C7pUQ7xe.js → use-password-reset-mode-Dbr2HEfw.js} +1 -1
- package/lib/public/assets/{users-DZ7B5ePh.js → users-CZqCE511.js} +1 -1
- package/lib/public/assets/{users-CIILdbvy.js → users-DxLHYmFm.js} +1 -1
- package/lib/public/assets/{video-off-aYil8B-X.js → video-off-mh1EEzP9.js} +1 -1
- package/lib/public/assets/{webhook-settings-17JYW1ZR.js → webhook-settings-BYuOib-Q.js} +1 -1
- package/lib/public/assets/{zap-D9hchm-a.js → zap-BOpUur0M.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +6 -67
- package/lib/src/device-managers/ios/IOSStreamService.js +15 -80
- package/lib/src/device-managers/ios/IOSTunnels.js +25 -16
- package/lib/src/device-managers/ios/WDAClient.js +65 -86
- package/lib/src/device-managers/ios/goIosBinary.js +16 -0
- package/lib/src/plugin.js +2 -10
- package/lib/src/services/PortAllocator.js +8 -1
- package/lib/src/services/ServerManager.js +22 -0
- package/lib/test/unit/PortAllocator.test.js +1 -1
- package/lib/test/unit/boot-go-ios-reap.spec.js +80 -0
- package/lib/test/unit/ios-discovery-same-host.spec.js +0 -1
- package/lib/test/unit/ios-no-network-fallback.spec.js +219 -0
- package/lib/test/unit/ios-stream-appium-tunnel.spec.js +0 -1
- package/lib/test/unit/ios-stream-port-leases.spec.js +0 -1
- package/lib/test/unit/ios-tunnels.spec.js +25 -3
- package/lib/test/unit/port-allocator-pairs.spec.js +34 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
2
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
7
|
};
|
|
24
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
-
var ownKeys = function(o) {
|
|
26
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
-
var ar = [];
|
|
28
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
-
return ar;
|
|
30
|
-
};
|
|
31
|
-
return ownKeys(o);
|
|
32
|
-
};
|
|
33
|
-
return function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod;
|
|
35
|
-
var result = {};
|
|
36
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
-
__setModuleDefault(result, mod);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
})();
|
|
41
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
10
|
};
|
|
@@ -61,10 +28,7 @@ const lodash_1 = require("lodash");
|
|
|
61
28
|
const appium_ios_device_1 = require("appium-ios-device");
|
|
62
29
|
const appleIdentity_1 = require("./appleIdentity");
|
|
63
30
|
const deviceIdentity_1 = require("../deviceIdentity");
|
|
64
|
-
const helpers_1 = require("../../helpers");
|
|
65
31
|
const logger_1 = __importDefault(require("../../logger"));
|
|
66
|
-
const path_1 = __importDefault(require("path"));
|
|
67
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
68
32
|
const device_utils_1 = require("../../device-utils");
|
|
69
33
|
const device_store_1 = require("../../data-service/device-store");
|
|
70
34
|
const PluginContext_1 = require("../../PluginContext");
|
|
@@ -161,8 +125,7 @@ let IOSDiscoveryService = class IOSDiscoveryService {
|
|
|
161
125
|
try {
|
|
162
126
|
const existingDevice = existingDeviceDetails.find((device) => device.udid === udid && device.host === ownHost);
|
|
163
127
|
if (existingDevice) {
|
|
164
|
-
|
|
165
|
-
return Object.assign(Object.assign({}, existingDevice), { ip: networkIp || (0, networkAddresses_1.sanitizeDeviceNetworkIp)(existingDevice.ip) || '' });
|
|
128
|
+
return Object.assign(Object.assign({}, existingDevice), { ip: (0, networkAddresses_1.sanitizeDeviceNetworkIp)(existingDevice.ip) || '' });
|
|
166
129
|
}
|
|
167
130
|
return yield this.getDeviceInfo(udid);
|
|
168
131
|
}
|
|
@@ -193,7 +156,6 @@ let IOSDiscoveryService = class IOSDiscoveryService {
|
|
|
193
156
|
const totalUtilizationTimeMilliSec = yield (0, device_utils_1.getUtilizationTime)(udid);
|
|
194
157
|
let sdk = 'Unknown';
|
|
195
158
|
let name = 'iPhone';
|
|
196
|
-
const ipAddress = yield this.fetchRealDeviceNetworkIp(udid);
|
|
197
159
|
try {
|
|
198
160
|
[sdk, name] = yield Promise.all([appium_ios_device_1.utilities.getOSVersion(udid), appium_ios_device_1.utilities.getDeviceName(udid)]);
|
|
199
161
|
}
|
|
@@ -212,7 +174,11 @@ let IOSDiscoveryService = class IOSDiscoveryService {
|
|
|
212
174
|
mjpegServerPort,
|
|
213
175
|
udid,
|
|
214
176
|
sdk,
|
|
215
|
-
name,
|
|
177
|
+
name,
|
|
178
|
+
// go-ios can't tell a phone's network address (its lockdown values
|
|
179
|
+
// carry none), and Xenon reaches a phone only through its forwarded
|
|
180
|
+
// ports, so nothing asks for it.
|
|
181
|
+
ip: '', busy: false, realDevice: true, deviceType: 'real', cpuArchitecture: 'arm64', platform: (name.toLowerCase().includes('tv') ? 'tvos' : 'ios'), host: host, totalUtilizationTimeMilliSec, sessionStartTime: 0, state: (storeDevice === null || storeDevice === void 0 ? void 0 : storeDevice.state) || 'Unknown', userBlocked: (storeDevice === null || storeDevice === void 0 ? void 0 : storeDevice.userBlocked) || false }, (storeDevice || {})), (0, deviceIdentity_1.nonNullIdentity)(identity));
|
|
216
182
|
});
|
|
217
183
|
}
|
|
218
184
|
getSimulators() {
|
|
@@ -281,33 +247,6 @@ let IOSDiscoveryService = class IOSDiscoveryService {
|
|
|
281
247
|
resolveNodeLanIp() {
|
|
282
248
|
return (0, networkAddresses_1.resolveAdvertisedBindHost)(this.pluginArgs.bindHostOrIp);
|
|
283
249
|
}
|
|
284
|
-
fetchRealDeviceNetworkIp(udid) {
|
|
285
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
-
return (0, networkAddresses_1.sanitizeDeviceNetworkIp)(yield this.fetchIpViaGoIos(udid));
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
fetchIpViaGoIos(udid) {
|
|
290
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
-
const goIOSDir = (0, helpers_1.cachePath)('goIOS');
|
|
292
|
-
const goIOSPath = path_1.default.join(goIOSDir, 'ios');
|
|
293
|
-
if (!fs_extra_1.default.existsSync(goIOSPath))
|
|
294
|
-
return '';
|
|
295
|
-
const { exec } = yield Promise.resolve().then(() => __importStar(require('child_process')));
|
|
296
|
-
const { promisify } = yield Promise.resolve().then(() => __importStar(require('util')));
|
|
297
|
-
const execPromise = promisify(exec);
|
|
298
|
-
try {
|
|
299
|
-
const { stdout } = yield execPromise(`"${goIOSPath}" info --udid ${udid}`, {
|
|
300
|
-
env: Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }),
|
|
301
|
-
});
|
|
302
|
-
const info = JSON.parse(stdout);
|
|
303
|
-
return info.IPAddress || '';
|
|
304
|
-
}
|
|
305
|
-
catch (e) {
|
|
306
|
-
logger_1.default.debug(`Failed to fetch IP via go-ios for ${udid}: ${e}`);
|
|
307
|
-
return '';
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
250
|
trackIOSDevices() {
|
|
312
251
|
if (this.trackingInitialized)
|
|
313
252
|
return;
|
|
@@ -74,7 +74,6 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
74
74
|
const tcp_port_used_1 = __importDefault(require("tcp-port-used"));
|
|
75
75
|
const InternalHttpClient_1 = require("../../InternalHttpClient");
|
|
76
76
|
const logger_1 = __importDefault(require("../../logger"));
|
|
77
|
-
const helpers_1 = require("../../helpers");
|
|
78
77
|
const singleFlight_1 = require("../../helpers/singleFlight");
|
|
79
78
|
const PortAllocator_1 = require("../../services/PortAllocator");
|
|
80
79
|
const device_store_1 = require("../../data-service/device-store");
|
|
@@ -82,6 +81,7 @@ const ownDeviceRow_1 = require("../ownDeviceRow");
|
|
|
82
81
|
const iosStreamDiagnostics_1 = require("./iosStreamDiagnostics");
|
|
83
82
|
const tunnelProcess_1 = require("./tunnelProcess");
|
|
84
83
|
const IOSTunnels_1 = require("./IOSTunnels");
|
|
84
|
+
const goIosBinary_1 = require("./goIosBinary");
|
|
85
85
|
const device_service_1 = require("../../data-service/device-service");
|
|
86
86
|
const manualLock_1 = require("../../services/recording/manualLock");
|
|
87
87
|
const recording_store_1 = require("../../services/recording/recording-store");
|
|
@@ -119,11 +119,7 @@ let IOSStreamService = class IOSStreamService {
|
|
|
119
119
|
this.startFlight = new singleFlight_1.SingleFlight();
|
|
120
120
|
this.recoveryCooldowns = new Map(); // Track last recovery attempt time
|
|
121
121
|
this.RECOVERY_COOLDOWN_MS = 30000; // 30s cooldown between recovery attempts
|
|
122
|
-
|
|
123
|
-
// which refreshes it each tick, so a long-lived stream's port never expires
|
|
124
|
-
// and gets reallocated to another device; stopStream() releases it explicitly.
|
|
125
|
-
this.STREAM_PORT_TTL_MS = 90 * 60 * 1000; // 1.5h
|
|
126
|
-
this.goIOSPath = this.getGoIOSPath();
|
|
122
|
+
this.goIOSPath = (0, goIosBinary_1.goIosBinaryPath)();
|
|
127
123
|
this.startWatchdog();
|
|
128
124
|
}
|
|
129
125
|
/**
|
|
@@ -143,8 +139,8 @@ let IOSStreamService = class IOSStreamService {
|
|
|
143
139
|
// its ports to another device mid-stream (TTL > this 1h interval).
|
|
144
140
|
try {
|
|
145
141
|
const portAllocator = typedi_2.Container.get(PortAllocator_1.PortAllocator);
|
|
146
|
-
yield portAllocator.touch(session.wdaPort,
|
|
147
|
-
yield portAllocator.touch(session.mjpegPort,
|
|
142
|
+
yield portAllocator.touch(session.wdaPort, IOSTunnels_1.IOS_STREAM_LEASE_TTL_MS);
|
|
143
|
+
yield portAllocator.touch(session.mjpegPort, IOSTunnels_1.IOS_STREAM_LEASE_TTL_MS);
|
|
148
144
|
yield this.tunnels().touch(udid);
|
|
149
145
|
}
|
|
150
146
|
catch (_a) {
|
|
@@ -223,10 +219,6 @@ let IOSStreamService = class IOSStreamService {
|
|
|
223
219
|
}
|
|
224
220
|
});
|
|
225
221
|
}
|
|
226
|
-
getGoIOSPath() {
|
|
227
|
-
const goIOSDir = (0, helpers_1.cachePath)('goIOS');
|
|
228
|
-
return path_1.default.join(goIOSDir, 'ios');
|
|
229
|
-
}
|
|
230
222
|
/**
|
|
231
223
|
* Check if go-ios binary exists
|
|
232
224
|
*/
|
|
@@ -409,9 +401,10 @@ let IOSStreamService = class IOSStreamService {
|
|
|
409
401
|
});
|
|
410
402
|
}
|
|
411
403
|
/**
|
|
412
|
-
* Comprehensive process-level and endpoint-level health check
|
|
413
|
-
*
|
|
414
|
-
*
|
|
404
|
+
* Comprehensive process-level and endpoint-level health check: WDA and its
|
|
405
|
+
* forwarders are running, and WDA answers on the stream's own port. Only
|
|
406
|
+
* that port: WDA names no phone, so nothing that answers elsewhere (a
|
|
407
|
+
* simulator's address is this Mac's own) is known to be this phone's.
|
|
415
408
|
*/
|
|
416
409
|
isStreamResponsive(udid) {
|
|
417
410
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -427,78 +420,18 @@ let IOSStreamService = class IOSStreamService {
|
|
|
427
420
|
return false;
|
|
428
421
|
}
|
|
429
422
|
if (!isWdaIproxyAlive || !isMjpegIproxyAlive) {
|
|
430
|
-
logger_1.default.warn(`🛡️ [${udid}] [Watchdog]
|
|
431
|
-
|
|
432
|
-
if (device && device.ip) {
|
|
433
|
-
// Double check if WDA is alive via network IP
|
|
434
|
-
const isWdaAccessibleViaNetwork = yield this.isWDARunningOnHost(device.ip, 8100);
|
|
435
|
-
if (isWdaAccessibleViaNetwork) {
|
|
436
|
-
logger_1.default.info(`🛡️ [${udid}] [Watchdog] WDA is alive on network ${device.ip}. Restarting tunnels...`);
|
|
437
|
-
yield this.restartTunnelsOnly(session);
|
|
438
|
-
return true; // We healed it!
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return false; // Cannot heal without network access or if WDA is also dead
|
|
423
|
+
logger_1.default.warn(`🛡️ [${udid}] [Watchdog] Port forwarder processes are dead.`);
|
|
424
|
+
return false;
|
|
442
425
|
}
|
|
443
426
|
// 2. Network Check: verify endpoint is responding via tunnel
|
|
444
427
|
const isRespondingViaTunnel = yield this.isWDARunning(session.wdaPort);
|
|
445
428
|
if (!isRespondingViaTunnel) {
|
|
446
|
-
logger_1.default.warn(`🛡️ [${udid}] [Watchdog] WDA
|
|
447
|
-
const device = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
448
|
-
if (device && device.ip) {
|
|
449
|
-
const isWdaAccessibleViaNetwork = yield this.isWDARunningOnHost(device.ip, 8100);
|
|
450
|
-
if (isWdaAccessibleViaNetwork) {
|
|
451
|
-
logger_1.default.info(`🛡️ [${udid}] [Watchdog] WDA is alive on network but tunnel is hung. Restarting tunnels...`);
|
|
452
|
-
yield this.restartTunnelsOnly(session);
|
|
453
|
-
return true;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
429
|
+
logger_1.default.warn(`🛡️ [${udid}] [Watchdog] WDA on port ${session.wdaPort} is unresponsive.`);
|
|
456
430
|
return false;
|
|
457
431
|
}
|
|
458
432
|
return true;
|
|
459
433
|
});
|
|
460
434
|
}
|
|
461
|
-
/**
|
|
462
|
-
* Check if WDA is running on a specific host/port
|
|
463
|
-
*/
|
|
464
|
-
isWDARunningOnHost(host, port) {
|
|
465
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
466
|
-
var _a, _b;
|
|
467
|
-
const axios = (yield Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
468
|
-
try {
|
|
469
|
-
const response = yield axios.get(`http://${host}:${port}/status`, {
|
|
470
|
-
timeout: 3000,
|
|
471
|
-
validateStatus: (status) => status === 200,
|
|
472
|
-
});
|
|
473
|
-
return ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.ready) === true;
|
|
474
|
-
}
|
|
475
|
-
catch (e) {
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Restarts only the iproxy tunnels without stopping WDA
|
|
482
|
-
*/
|
|
483
|
-
restartTunnelsOnly(session) {
|
|
484
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
485
|
-
const udid = session.udid;
|
|
486
|
-
const isolationService = typedi_2.Container.get(ResourceIsolationService_1.ResourceIsolationService);
|
|
487
|
-
// 1. Kill old tunnels
|
|
488
|
-
if (session.forwardWDAProcess)
|
|
489
|
-
session.forwardWDAProcess.kill('SIGKILL');
|
|
490
|
-
if (session.forwardMJPEGProcess)
|
|
491
|
-
session.forwardMJPEGProcess.kill('SIGKILL');
|
|
492
|
-
// 2. Start new tunnels
|
|
493
|
-
const wdaIproxy = isolationService.wrapSpawn('iproxy', ['-u', udid, `${session.wdaPort}:8100`], 'Performance');
|
|
494
|
-
const mjpegIproxy = isolationService.wrapSpawn('iproxy', ['-u', udid, `${session.mjpegPort}:9100`], 'Performance');
|
|
495
|
-
session.forwardWDAProcess = (0, child_process_1.spawn)(wdaIproxy.command, wdaIproxy.args);
|
|
496
|
-
typedi_2.Container.get(ProcessRegistry_1.ProcessRegistry).track({ kind: 'other', udid, process: session.forwardWDAProcess });
|
|
497
|
-
session.forwardMJPEGProcess = (0, child_process_1.spawn)(mjpegIproxy.command, mjpegIproxy.args);
|
|
498
|
-
typedi_2.Container.get(ProcessRegistry_1.ProcessRegistry).track({ kind: 'ios-mjpeg', udid, process: session.forwardMJPEGProcess });
|
|
499
|
-
logger_1.default.info(`🛡️ [${udid}] [Watchdog] Tunnels restarted successfully.`);
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
435
|
/**
|
|
503
436
|
* Start streaming for a device
|
|
504
437
|
*/
|
|
@@ -594,7 +527,7 @@ let IOSStreamService = class IOSStreamService {
|
|
|
594
527
|
// guarantee a non-colliding port; the lease is refreshed by the watchdog
|
|
595
528
|
// and released in stopStream().
|
|
596
529
|
const portAllocator = typedi_2.Container.get(PortAllocator_1.PortAllocator);
|
|
597
|
-
const ttl = { ttlMs:
|
|
530
|
+
const ttl = { ttlMs: IOSTunnels_1.IOS_STREAM_LEASE_TTL_MS };
|
|
598
531
|
// Principal Discovery: attach to the WDA an Appium session runs on this
|
|
599
532
|
// device instead of launching a second one over it. The one exception
|
|
600
533
|
// to the rule above: that session's forwarder listens on the Device
|
|
@@ -1101,7 +1034,9 @@ let IOSStreamService = class IOSStreamService {
|
|
|
1101
1034
|
* server owns no legitimate tunnel — i.e. on fresh boot (orphans from a
|
|
1102
1035
|
* previous run, or a hard-killed / crashed process whose graceful cleanup
|
|
1103
1036
|
* never ran) or during full shutdown. This is the catch-all that stops the
|
|
1104
|
-
* self-forking go-ios agent storm from surviving across restarts.
|
|
1037
|
+
* self-forking go-ios agent storm from surviving across restarts. It kills
|
|
1038
|
+
* this process's own go-ios calls too, so boot runs it before device
|
|
1039
|
+
* detection starts (ServerManager.reapLeftoverGoIos).
|
|
1105
1040
|
*/
|
|
1106
1041
|
reapOrphanTunnels() {
|
|
1107
1042
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.IOSTunnels = exports.TUNNEL_BORROW_IDLE_MS = exports.TUNNEL_CHECK_MS = exports.
|
|
21
|
+
exports.IOSTunnels = exports.TUNNEL_BORROW_IDLE_MS = exports.TUNNEL_CHECK_MS = exports.IOS_STREAM_LEASE_TTL_MS = exports.TUNNEL_READY_POLL_MS = exports.TUNNEL_READY_TIMEOUT_MS = void 0;
|
|
22
22
|
exports.iosVersionOf = iosVersionOf;
|
|
23
23
|
exports.goIosReason = goIosReason;
|
|
24
24
|
/**
|
|
@@ -48,25 +48,26 @@ exports.goIosReason = goIosReason;
|
|
|
48
48
|
const typedi_1 = require("typedi");
|
|
49
49
|
const child_process_1 = require("child_process");
|
|
50
50
|
const http_1 = __importDefault(require("http"));
|
|
51
|
-
const path_1 = __importDefault(require("path"));
|
|
52
51
|
const util_1 = require("util");
|
|
53
52
|
const logger_1 = __importDefault(require("../../logger"));
|
|
54
|
-
const helpers_1 = require("../../helpers");
|
|
55
53
|
const PortAllocator_1 = require("../../services/PortAllocator");
|
|
56
54
|
const ProcessRegistry_1 = require("../../services/ProcessRegistry");
|
|
57
55
|
const ResourceIsolationService_1 = require("../../services/ResourceIsolationService");
|
|
58
56
|
const iosStreamDiagnostics_1 = require("./iosStreamDiagnostics");
|
|
57
|
+
const goIosBinary_1 = require("./goIosBinary");
|
|
59
58
|
const tunnelProcess_1 = require("./tunnelProcess");
|
|
60
59
|
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
61
60
|
/** How long a new tunnel has to answer for its phone before the start goes on without it. */
|
|
62
61
|
exports.TUNNEL_READY_TIMEOUT_MS = 20000;
|
|
63
62
|
exports.TUNNEL_READY_POLL_MS = 500;
|
|
64
63
|
/**
|
|
65
|
-
*
|
|
66
|
-
* (IOSStreamService's
|
|
67
|
-
*
|
|
64
|
+
* How long an iPhone stream's port leases last: its WDA and MJPEG ports
|
|
65
|
+
* (IOSStreamService) and its tunnel's pair. Longer than the stream watchdog's
|
|
66
|
+
* hourly tick, which refreshes them (the tunnel's through
|
|
67
|
+
* {@link IOSTunnels.touch}), so a long stream never loses its ports to another
|
|
68
|
+
* phone. Stopping the stream or the tunnel releases them.
|
|
68
69
|
*/
|
|
69
|
-
exports.
|
|
70
|
+
exports.IOS_STREAM_LEASE_TTL_MS = 90 * 60 * 1000;
|
|
70
71
|
/** How often running tunnels are checked for a phone that went away. */
|
|
71
72
|
exports.TUNNEL_CHECK_MS = 5000;
|
|
72
73
|
/**
|
|
@@ -126,7 +127,7 @@ let IOSTunnels = class IOSTunnels {
|
|
|
126
127
|
this.tunnels = new Map();
|
|
127
128
|
this.checking = false;
|
|
128
129
|
this.versionCache = new Map();
|
|
129
|
-
this.goIOSPath =
|
|
130
|
+
this.goIOSPath = (0, goIosBinary_1.goIosBinaryPath)();
|
|
130
131
|
}
|
|
131
132
|
/**
|
|
132
133
|
* The phone's tunnel-info port for its stream, starting its tunnel if it
|
|
@@ -169,7 +170,7 @@ let IOSTunnels = class IOSTunnels {
|
|
|
169
170
|
if (version === null || version < 17)
|
|
170
171
|
return null;
|
|
171
172
|
const port = yield this.allocator().acquirePair('tunnel', udid, {
|
|
172
|
-
ttlMs: exports.
|
|
173
|
+
ttlMs: exports.IOS_STREAM_LEASE_TTL_MS,
|
|
173
174
|
});
|
|
174
175
|
let proc;
|
|
175
176
|
try {
|
|
@@ -209,7 +210,9 @@ let IOSTunnels = class IOSTunnels {
|
|
|
209
210
|
void this.releasePair(port, udid);
|
|
210
211
|
};
|
|
211
212
|
proc.once('exit', ended);
|
|
212
|
-
|
|
213
|
+
// `on`, not `once`: a second 'error' with no listener left would be
|
|
214
|
+
// thrown, and take the server down. ended() only acts once.
|
|
215
|
+
proc.on('error', (err) => {
|
|
213
216
|
this.log.warn(`[${udid}] go-ios tunnel failed: ${err.message}`);
|
|
214
217
|
ended();
|
|
215
218
|
});
|
|
@@ -230,10 +233,16 @@ let IOSTunnels = class IOSTunnels {
|
|
|
230
233
|
waitUntilReady(udid, tunnel) {
|
|
231
234
|
return __awaiter(this, void 0, void 0, function* () {
|
|
232
235
|
const { port, process: proc } = tunnel;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
// By the clock, not a count of checks: each check can take up to its own
|
|
237
|
+
// 1 s timeout, which stretched 41 checks to about a minute.
|
|
238
|
+
const deadline = this.now() + exports.TUNNEL_READY_TIMEOUT_MS;
|
|
239
|
+
for (let first = true;; first = false) {
|
|
240
|
+
if (!first) {
|
|
241
|
+
const left = deadline - this.now();
|
|
242
|
+
if (left <= 0)
|
|
243
|
+
break;
|
|
244
|
+
yield this.sleep(Math.min(exports.TUNNEL_READY_POLL_MS, left));
|
|
245
|
+
}
|
|
237
246
|
if (this.tunnels.get(udid) !== tunnel) {
|
|
238
247
|
throw new Error(`The go-ios tunnel for ${udid} exited before it was ready (exit code ${proc.exitCode})` +
|
|
239
248
|
(tunnel.reason ? `: ${tunnel.reason}` : ''));
|
|
@@ -376,8 +385,8 @@ let IOSTunnels = class IOSTunnels {
|
|
|
376
385
|
if (!tunnel)
|
|
377
386
|
return;
|
|
378
387
|
const allocator = this.allocator();
|
|
379
|
-
yield allocator.touch(tunnel.port, exports.
|
|
380
|
-
yield allocator.touch(tunnel.port + 1, exports.
|
|
388
|
+
yield allocator.touch(tunnel.port, exports.IOS_STREAM_LEASE_TTL_MS);
|
|
389
|
+
yield allocator.touch(tunnel.port + 1, exports.IOS_STREAM_LEASE_TTL_MS);
|
|
381
390
|
});
|
|
382
391
|
}
|
|
383
392
|
releasePair(port, udid) {
|
|
@@ -92,96 +92,75 @@ let WDAClient = WDAClient_1 = class WDAClient {
|
|
|
92
92
|
}
|
|
93
93
|
const prefixes = (cached === null || cached === void 0 ? void 0 : cached.sessionId) ? [`/session/${cached.sessionId}`] : ['', '/session/any'];
|
|
94
94
|
let lastError;
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
? yield axios_1.default.post(url, data || {}, { timeout })
|
|
130
|
-
: yield axios_1.default.get(url, { timeout });
|
|
131
|
-
// If we reached a host but it returned a logical error (e.g. 404 for session),
|
|
132
|
-
// we should update our cache and potentially stop retrying this host/prefix combo.
|
|
133
|
-
// However, for simplicity, we treat any successful HTTP response as "Host is alive".
|
|
134
|
-
if (isNetworkHost && targetHost !== (cached === null || cached === void 0 ? void 0 : cached.host)) {
|
|
135
|
-
this.log.debug(`[WDA] Successful fallback to network IP ${targetHost} for ${udid}`);
|
|
136
|
-
}
|
|
137
|
-
const sid = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.sessionId) || ((_c = (_b = res.data) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.sessionId);
|
|
138
|
-
if (sid && sid !== (cached === null || cached === void 0 ? void 0 : cached.sessionId)) {
|
|
139
|
-
this.log.debug(`[WDA] Detected new session ID: ${sid} via ${targetHost}`);
|
|
140
|
-
this.wdaConnectionCache.set(cacheKey, {
|
|
141
|
-
host: targetHost,
|
|
142
|
-
pathPrefix: `/session/${sid}`,
|
|
143
|
-
sessionId: sid,
|
|
144
|
-
});
|
|
145
|
-
typedi_1.Container.get(IOSStreamService_1.default).setWDASessionId(udid, sid);
|
|
146
|
-
}
|
|
147
|
-
return res;
|
|
95
|
+
// Only the phone's own forwarded port. WDA names no phone, so no address
|
|
96
|
+
// on the network is known to be this phone's: a simulator's is this
|
|
97
|
+
// Mac's own, where port 8100 can be an iPhone's WDA forward.
|
|
98
|
+
const targetHost = '127.0.0.1';
|
|
99
|
+
for (const prefix of prefixes) {
|
|
100
|
+
try {
|
|
101
|
+
const isSessionless = !(options === null || options === void 0 ? void 0 : options.useSessionPath) &&
|
|
102
|
+
(['/status', '/health', '/wda/healthcheck'].includes(endpoint));
|
|
103
|
+
const url = `http://${targetHost}:${port}${isSessionless ? '' : prefix}${endpoint}`;
|
|
104
|
+
// Principal Stability: Screenshots, activation, and script execution need longer timeouts.
|
|
105
|
+
// Heartbeats and Status checks should be snappy to fail-over quickly.
|
|
106
|
+
const isHeavyCommand = endpoint.includes('screenshot') ||
|
|
107
|
+
endpoint.includes('source') ||
|
|
108
|
+
endpoint.includes('swipe') ||
|
|
109
|
+
endpoint.includes('touchAndHold') ||
|
|
110
|
+
endpoint.includes('activate') ||
|
|
111
|
+
endpoint.includes('execute') ||
|
|
112
|
+
endpoint.includes('actions');
|
|
113
|
+
const timeout = isHeavyCommand ? 30000 : 5000; // Snappy 5s timeout for standard commands
|
|
114
|
+
const res = method === 'post'
|
|
115
|
+
? yield axios_1.default.post(url, data || {}, { timeout })
|
|
116
|
+
: yield axios_1.default.get(url, { timeout });
|
|
117
|
+
// If we reached a host but it returned a logical error (e.g. 404 for session),
|
|
118
|
+
// we should update our cache and potentially stop retrying this host/prefix combo.
|
|
119
|
+
// However, for simplicity, we treat any successful HTTP response as "Host is alive".
|
|
120
|
+
const sid = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.sessionId) || ((_c = (_b = res.data) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.sessionId);
|
|
121
|
+
if (sid && sid !== (cached === null || cached === void 0 ? void 0 : cached.sessionId)) {
|
|
122
|
+
this.log.debug(`[WDA] Detected new session ID: ${sid} via ${targetHost}`);
|
|
123
|
+
this.wdaConnectionCache.set(cacheKey, {
|
|
124
|
+
host: targetHost,
|
|
125
|
+
pathPrefix: `/session/${sid}`,
|
|
126
|
+
sessionId: sid,
|
|
127
|
+
});
|
|
128
|
+
typedi_1.Container.get(IOSStreamService_1.default).setWDASessionId(udid, sid);
|
|
148
129
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
this.wdaConnectionCache.delete(cacheKey);
|
|
169
|
-
typedi_1.Container.get(IOSStreamService_1.default).setWDASessionId(udid, '');
|
|
170
|
-
return this.performWDACommand(udid, method, endpoint, data, retryCount + 1, options);
|
|
171
|
-
}
|
|
172
|
-
// Phase 2: Transient State Recovery (500)
|
|
173
|
-
// This handles "Focused" errors, "Internal" errors, or transient locks during app switches.
|
|
174
|
-
if (status === 500) {
|
|
175
|
-
this.log.warn(`[WDA] Transient error (500) for ${udid}, retrying in 1s... (Reason: ${errorMsg})`);
|
|
176
|
-
yield new Promise((r) => setTimeout(r, 1000));
|
|
177
|
-
return this.performWDACommand(udid, method, endpoint, data, retryCount + 1, options);
|
|
130
|
+
return res;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
lastError = err;
|
|
134
|
+
if (err.response) {
|
|
135
|
+
const status = err.response.status;
|
|
136
|
+
const errorMsg = JSON.stringify(err.response.data);
|
|
137
|
+
this.log.info(`[WDA] Command ${method.toUpperCase()} ${endpoint} failed (${status}): ${errorMsg}`);
|
|
138
|
+
// Principal Resiliency: Multi-Phase Recovery
|
|
139
|
+
if (retryCount < 2) {
|
|
140
|
+
// Phase 1: Dead Session Recovery (404)
|
|
141
|
+
if (status === 404 && prefix.includes('/session/')) {
|
|
142
|
+
// Special Intelligence: Some WDA versions don't support /execute, /wda/homescreen, etc.
|
|
143
|
+
// We should NOT clear the session if these specific commands fail with 404,
|
|
144
|
+
// as they might just be unsupported features.
|
|
145
|
+
const NON_FATAL_ENDPOINTS = ['execute', 'homescreen', 'pressButton'];
|
|
146
|
+
if (NON_FATAL_ENDPOINTS.some((e) => endpoint.includes(e))) {
|
|
147
|
+
this.log.debug(`[WDA] Command ${endpoint} failed with 404 (Unsupported or Logical Error). Skipping session reset.`);
|
|
148
|
+
throw err;
|
|
178
149
|
}
|
|
150
|
+
this.log.warn(`[WDA] Session ${cached === null || cached === void 0 ? void 0 : cached.sessionId} appears dead. Resetting and retrying...`);
|
|
151
|
+
this.wdaConnectionCache.delete(cacheKey);
|
|
152
|
+
typedi_1.Container.get(IOSStreamService_1.default).setWDASessionId(udid, '');
|
|
153
|
+
return this.performWDACommand(udid, method, endpoint, data, retryCount + 1, options);
|
|
154
|
+
}
|
|
155
|
+
// Phase 2: Transient State Recovery (500)
|
|
156
|
+
// This handles "Focused" errors, "Internal" errors, or transient locks during app switches.
|
|
157
|
+
if (status === 500) {
|
|
158
|
+
this.log.warn(`[WDA] Transient error (500) for ${udid}, retrying in 1s... (Reason: ${errorMsg})`);
|
|
159
|
+
yield new Promise((r) => setTimeout(r, 1000));
|
|
160
|
+
return this.performWDACommand(udid, method, endpoint, data, retryCount + 1, options);
|
|
179
161
|
}
|
|
180
|
-
throw err;
|
|
181
162
|
}
|
|
182
|
-
|
|
183
|
-
if (isNetworkHost && ['/status', '/health'].includes(endpoint))
|
|
184
|
-
break;
|
|
163
|
+
throw err;
|
|
185
164
|
}
|
|
186
165
|
}
|
|
187
166
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.goIosBinaryPath = goIosBinaryPath;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const helpers_1 = require("../../helpers");
|
|
9
|
+
/**
|
|
10
|
+
* The go-ios binary Xenon vendors, where src/scripts/install-go-ios.ts puts
|
|
11
|
+
* it. Every go-ios command Xenon runs uses this one path: the boot and
|
|
12
|
+
* shutdown reaps find go-ios processes by it.
|
|
13
|
+
*/
|
|
14
|
+
function goIosBinaryPath() {
|
|
15
|
+
return path_1.default.join((0, helpers_1.cachePath)('goIOS'), 'ios');
|
|
16
|
+
}
|
package/lib/src/plugin.js
CHANGED
|
@@ -157,16 +157,8 @@ class XenonPlugin extends base_plugin_1.default {
|
|
|
157
157
|
catch (err) {
|
|
158
158
|
logger_1.default.warn(`[plugin] RecordingOrchestrator.recoverOnBoot failed: ${err === null || err === void 0 ? void 0 : err.message}`);
|
|
159
159
|
}
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
// orphans that would otherwise keep self-forking into a runaway process storm.
|
|
163
|
-
try {
|
|
164
|
-
const { default: IOSStreamService } = yield Promise.resolve().then(() => __importStar(require('./device-managers/ios/IOSStreamService')));
|
|
165
|
-
yield typedi_1.Container.get(IOSStreamService).reapOrphanTunnels();
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
logger_1.default.warn(`[plugin] IOSStreamService.reapOrphanTunnels failed: ${err === null || err === void 0 ? void 0 : err.message}`);
|
|
169
|
-
}
|
|
160
|
+
// Leftover go-ios is reaped by ServerManager.updateServer, before device
|
|
161
|
+
// detection starts: here, the reap killed detection's own go-ios calls.
|
|
170
162
|
});
|
|
171
163
|
}
|
|
172
164
|
createSession(next, driver, jwpDesCaps, jwpReqCaps, caps) {
|
|
@@ -291,9 +291,16 @@ let PortAllocator = class PortAllocator {
|
|
|
291
291
|
.catch(() => undefined);
|
|
292
292
|
});
|
|
293
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Delete a phone's leases when a session on it ends. Not its go-ios
|
|
296
|
+
* tunnel's pair: the phone's stream, and so its tunnel, can outlive the
|
|
297
|
+
* session, and IOSTunnels gives the pair back when the tunnel stops.
|
|
298
|
+
*/
|
|
294
299
|
releaseForUdid(udid) {
|
|
295
300
|
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
-
yield prisma_1.prisma.portLease.deleteMany({
|
|
301
|
+
yield prisma_1.prisma.portLease.deleteMany({
|
|
302
|
+
where: { leasedToUdid: udid, purpose: { not: 'tunnel' } },
|
|
303
|
+
});
|
|
297
304
|
});
|
|
298
305
|
}
|
|
299
306
|
purgeExpired() {
|