@xenon-device-management/xenon 2.13.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-Cvpkk4r9.js → AnnotationOverlay-B_VNtKBH.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-Bhn5ZtVM.js → ApiKeyGate-BwuaMjkX.js} +1 -1
- package/lib/public/assets/{BugReportButton-BA6aEjTC.js → BugReportButton-BAt0_pzq.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +2 -0
- package/lib/public/assets/{EmptyState-CgzbuUvO.js → EmptyState-BSjhk_IV.js} +1 -1
- package/lib/public/assets/{FieldGroup-BHc_5qNc.js → FieldGroup-Cb5QZsE1.js} +1 -1
- package/lib/public/assets/{FilterMenu-D7OMGuRc.js → FilterMenu-BAaSlWbW.js} +1 -1
- package/lib/public/assets/{Menu-C173JJ0l.js → Menu-JGGYrSQQ.js} +1 -1
- package/lib/public/assets/{Modal-ChvVUI3B.js → Modal-CFCp3n9d.js} +1 -1
- package/lib/public/assets/{RecordingPage-Dhw4d_O6.js → RecordingPage-CbJj_EBT.js} +1 -1
- package/lib/public/assets/{RecordingsPage-z97ftGK-.js → RecordingsPage-BJeYnygR.js} +2 -2
- package/lib/public/assets/{SegmentedControl-DbXhBGZC.js → SegmentedControl-Bg42Atjy.js} +1 -1
- package/lib/public/assets/{SettingCard-C908GgiI.js → SettingCard-Njz1pUIJ.js} +1 -1
- package/lib/public/assets/{Table-B1DB_ZN-.js → Table-BcTm5xV2.js} +1 -1
- package/lib/public/assets/{activity-Css0xNvp.js → activity-DOVdsfMQ.js} +1 -1
- package/lib/public/assets/{ai-settings-BMQK3-yN.js → ai-settings-CsuJroRf.js} +2 -2
- package/lib/public/assets/{api-keys-CbJ42eDQ.js → api-keys-BKnNSTw9.js} +1 -1
- package/lib/public/assets/{apps-DW1pw2re.js → apps-D0SC6uw6.js} +2 -2
- package/lib/public/assets/{arrow-left-Dpog9L0F.js → arrow-left-MMbTmgoE.js} +1 -1
- package/lib/public/assets/{arrow-right-CsBIlSIw.js → arrow-right-BMfnGmxW.js} +1 -1
- package/lib/public/assets/{arrow-up-right-z1tRQE4R.js → arrow-up-right-DuAftMWB.js} +1 -1
- package/lib/public/assets/{auth-shell-mXiX5vNk.js → auth-shell-nZC2JNDO.js} +2 -2
- package/lib/public/assets/{builds-page-BcFn23bE.js → builds-page-hDCgTwkS.js} +1 -1
- package/lib/public/assets/{button-Cq_zHETv.js → button-Dda4irn_.js} +1 -1
- package/lib/public/assets/{calendar-CKxABX9q.js → calendar-BMr0Q62v.js} +1 -1
- package/lib/public/assets/{check-C7xu-hGU.js → check-CZ9iw9-e.js} +1 -1
- package/lib/public/assets/{chevron-right-BiqfdCGA.js → chevron-right-BRYJJVC9.js} +1 -1
- package/lib/public/assets/{circle-check-CyBjjPxR.js → circle-check-CqAJjQ5O.js} +1 -1
- package/lib/public/assets/{circle-x-6gMRcx6z.js → circle-x-DlDJtnVz.js} +1 -1
- package/lib/public/assets/{clock-U1rDTT1h.js → clock-CpuDKysU.js} +1 -1
- package/lib/public/assets/{copy-BIM0ZX0L.js → copy-CAQydNFe.js} +1 -1
- package/lib/public/assets/device-explorer-CdO6_nrn.js +282 -0
- package/lib/public/assets/{download-DDBRXnAZ.js → download-C9XG6lLv.js} +1 -1
- package/lib/public/assets/{forgot-password-Dpg7yxM8.js → forgot-password-CGnkRgE9.js} +1 -1
- package/lib/public/assets/h264Stream-VQBphYhg.js +26 -0
- package/lib/public/assets/{index-BGBqohfQ.js → index-BbKEXUFH.js} +5 -5
- package/lib/public/assets/{index-BTYMAqNW.css → index-Bxoxt9sV.css} +1 -1
- package/lib/public/assets/{index-BQ2luT7L.js → index-DJGc1M2f.js} +1 -1
- package/lib/public/assets/{input-BvB1cjHE.js → input-Cm0tmSoc.js} +1 -1
- package/lib/public/assets/{line-chart-m78VggnW.js → line-chart-D2DyJCml.js} +1 -1
- package/lib/public/assets/{list-checks-cY7o1QTL.js → list-checks-UWf1GY99.js} +1 -1
- package/lib/public/assets/{lock-loz8txQm.js → lock-CCJv9m_I.js} +1 -1
- package/lib/public/assets/{login-BYOvz4lx.js → login-f8vOVW-U.js} +1 -1
- package/lib/public/assets/maintenance-settings-C1PrkuAH.js +6 -0
- package/lib/public/assets/{monitor-C4HPHb6s.js → monitor-B2jDgwm7.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BkdKtCzi.js → mouse-pointer-2-DLI1RtzU.js} +1 -1
- package/lib/public/assets/{network-C5e0MIWo.js → network-ebT86qoi.js} +1 -1
- package/lib/public/assets/{overview-tlu8ijGd.js → overview-C0aykVrd.js} +1 -1
- package/lib/public/assets/{page-header-FXPlo6az.js → page-header-BjzgWz-T.js} +1 -1
- package/lib/public/assets/{play-CWdk0_Sy.js → play-DLG9t8FU.js} +1 -1
- package/lib/public/assets/{plus-B2xCnntV.js → plus-nBFSMu_X.js} +1 -1
- package/lib/public/assets/{profile-page-D080qzr4.js → profile-page-CSl0UYlv.js} +1 -1
- package/lib/public/assets/{reset-password-D3u4Mbvn.js → reset-password-LeOuHqmB.js} +1 -1
- package/lib/public/assets/{runbook-page-hPRZMy7s.js → runbook-page-CMMeQml9.js} +1 -1
- package/lib/public/assets/{select-12RThAXc.js → select-88jHcl-Z.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-uQwvV7EQ.js → selector-detail-redirect-BbXKJjZM.js} +1 -1
- package/lib/public/assets/{selector-health-page-CThoxDFO.js → selector-health-page-B4rA1Xkv.js} +2 -2
- package/lib/public/assets/session-detail-page-COL_RZxw.js +4 -0
- package/lib/public/assets/settings-GF6ZMSQ6.js +6 -0
- package/lib/public/assets/{stat-tile-B02WEte8.js → stat-tile-BV98e_Sv.js} +1 -1
- package/lib/public/assets/{tablet-Dj30VGZl.js → tablet-BiAsnFDj.js} +1 -1
- package/lib/public/assets/{teams-uL4fjTDR.js → teams-B4-jrr-1.js} +1 -1
- package/lib/public/assets/{trash-2-B54e-TfY.js → trash-2-C5RSuX0e.js} +1 -1
- package/lib/public/assets/{upload-B3k8WCSP.js → upload-BG7_rMXK.js} +1 -1
- package/lib/public/assets/{use-builds-data-BFn90NH3.js → use-builds-data-J-riQYyC.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DL2TmWZ0.js → use-password-reset-mode-C8-eH-UG.js} +1 -1
- package/lib/public/assets/{users-CqBuxMlX.js → users-CFba5TSy.js} +1 -1
- package/lib/public/assets/{users-DjQSucKb.js → users-Cp0beJm3.js} +1 -1
- package/lib/public/assets/{video-off-T0rlt0GA.js → video-off-DDsEISjV.js} +1 -1
- package/lib/public/assets/webhook-settings-BGu32vSB.js +1 -0
- package/lib/public/assets/{zap-gBHqutZV.js → zap-l4VaXzq8.js} +1 -1
- package/lib/public/index.html +2 -2
- package/lib/schema.json +19 -46
- package/lib/src/XenonCapabilityManager.js +22 -5
- package/lib/src/app/openapi/control.yaml +71 -20
- package/lib/src/app/openapi/grid.yaml +23 -5
- package/lib/src/app/openapi/platform.yaml +136 -28
- package/lib/src/app/routers/control.js +30 -5
- package/lib/src/app/routers/dashboard.js +29 -1
- package/lib/src/app/routers/grid.js +32 -10
- package/lib/src/app/routers/streamStartConflict.js +3 -0
- package/lib/src/app/routers/streamType.js +7 -2
- package/lib/src/app/routers/webhook.js +8 -4
- package/lib/src/config.js +50 -5
- package/lib/src/dashboard/commands.js +185 -141
- package/lib/src/dashboard/event-manager.js +39 -6
- package/lib/src/data-service/appleFamily.js +43 -0
- package/lib/src/data-service/db.js +11 -0
- package/lib/src/data-service/device-service.js +46 -1
- package/lib/src/data-service/device-store.js +66 -3
- package/lib/src/data-service/deviceClaims.js +37 -0
- package/lib/src/data-service/deviceFieldOwners.js +25 -11
- package/lib/src/data-service/deviceSettings.js +79 -0
- package/lib/src/data-service/prisma-store.js +152 -9
- package/lib/src/data-service/web-config-service.js +43 -92
- package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
- package/lib/src/device-managers/ios/WDAClient.js +44 -43
- package/lib/src/device-managers/shellCommands.js +96 -0
- package/lib/src/device-utils.js +143 -37
- package/lib/src/generated/client/edge.js +17 -3
- package/lib/src/generated/client/index-browser.js +14 -0
- package/lib/src/generated/client/index.d.ts +2054 -379
- package/lib/src/generated/client/index.js +17 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +35 -7
- package/lib/src/generated/client/wasm.js +14 -0
- package/lib/src/interceptors/CommandInterceptor.js +29 -8
- package/lib/src/interceptors/xenonScripts.js +55 -0
- package/lib/src/interfaces/IPluginArgs.js +1 -2
- package/lib/src/middleware/deviceAccessGuard.js +63 -9
- package/lib/src/plugin.js +15 -1
- package/lib/src/prisma.js +1 -1
- package/lib/src/scripts/database-check.js +124 -0
- package/lib/src/services/AIService.js +99 -0
- package/lib/src/services/CleanupService.js +6 -1
- package/lib/src/services/InterceptorService.js +70 -13
- package/lib/src/services/NetworkConditioningService.js +85 -88
- package/lib/src/services/NotificationService.js +36 -29
- package/lib/src/services/OrphanSweeper.js +5 -0
- package/lib/src/services/PortAllocator.js +5 -0
- package/lib/src/services/ServerManager.js +97 -8
- package/lib/src/services/SessionLifecycleService.js +63 -53
- package/lib/src/services/ShutdownCoordinator.js +33 -0
- package/lib/src/services/TeamService.js +20 -3
- package/lib/src/services/device-access/SessionOwnerResolver.js +26 -0
- package/lib/src/services/device-access/deviceAccessPolicy.js +38 -6
- package/lib/src/services/device-access/leaseHold.js +26 -0
- package/lib/src/services/device-access/ticketActorAccess.js +6 -0
- package/lib/src/services/healing/HealingOrchestrator.js +32 -1
- package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
- package/lib/src/services/interceptor/interceptorOptions.js +44 -0
- package/lib/src/services/lease/activeLeases.js +37 -0
- package/lib/src/services/network/AndroidRadios.js +71 -0
- package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
- package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
- package/lib/src/services/network/adbForPhone.js +60 -0
- package/lib/src/services/network/phoneNetworkLock.js +20 -0
- package/lib/src/services/network/xenonProxy.js +107 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +53 -43
- package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
- package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
- package/lib/src/services/omni-vision/screenScale.js +68 -0
- package/lib/src/services/recording/busy-precheck.js +24 -3
- package/lib/src/services/recording/concurrency-gate.js +12 -3
- package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
- package/lib/src/services/settings/labSettings.js +149 -0
- package/lib/src/services/webhookEvents.js +173 -0
- package/lib/test/helpers/fake-adb.js +58 -0
- package/lib/test/helpers/fake-device-table.js +63 -24
- package/lib/test/helpers/scratch-database.js +1 -0
- package/lib/test/unit/TeamService.test.js +3 -0
- package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
- package/lib/test/unit/apple-family.spec.js +36 -0
- package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
- package/lib/test/unit/cleanup-settings.spec.js +244 -0
- package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
- package/lib/test/unit/dashboard-commands.spec.js +249 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
- package/lib/test/unit/database-check.spec.js +108 -0
- package/lib/test/unit/default-plugin-args.spec.js +33 -0
- package/lib/test/unit/device-access-lease.spec.js +270 -0
- package/lib/test/unit/device-offline-webhook.spec.js +81 -0
- package/lib/test/unit/device-setting-columns.spec.js +66 -0
- package/lib/test/unit/device-settings-loki.spec.js +87 -0
- package/lib/test/unit/device-settings-survive.spec.js +555 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
- package/lib/test/unit/emulator-boot.spec.js +91 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +48 -0
- package/lib/test/unit/hub-node-same-host.spec.js +5 -0
- package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
- package/lib/test/unit/json-logging-option.spec.js +74 -0
- package/lib/test/unit/lab-settings.spec.js +218 -0
- package/lib/test/unit/lease/allocate-lease-branch.spec.js +22 -0
- package/lib/test/unit/lease/session-lease-proof.spec.js +59 -0
- package/lib/test/unit/lease-held-phone.spec.js +196 -0
- package/lib/test/unit/max-sessions.spec.js +240 -0
- package/lib/test/unit/ocr-text-match.spec.js +44 -0
- package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +161 -0
- package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
- package/lib/test/unit/omni-vision.spec.js +1 -1
- package/lib/test/unit/partial-config-file.spec.js +114 -0
- package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
- package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
- package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
- package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
- package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
- package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
- package/lib/test/unit/phone-network/session-start.spec.js +110 -0
- package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
- package/lib/test/unit/plugin.spec.js +3 -3
- package/lib/test/unit/recording-options.spec.js +181 -0
- package/lib/test/unit/schema-required-args.spec.js +21 -16
- package/lib/test/unit/self-healing-boot.spec.js +75 -0
- package/lib/test/unit/self-healing-setting.spec.js +272 -0
- package/lib/test/unit/session-failed-webhook.spec.js +170 -0
- package/lib/test/unit/shell-commands.spec.js +133 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
- package/lib/test/unit/stream-start-pipelines.spec.js +210 -0
- package/lib/test/unit/stream-status-type.spec.js +4 -0
- package/lib/test/unit/vision-assertion.spec.js +118 -0
- package/lib/test/unit/wda-client-shell.spec.js +133 -0
- package/lib/test/unit/web-config-service.spec.js +79 -0
- package/lib/test/unit/webhook-delivery.spec.js +24 -0
- package/lib/test/unit/webhook-payloads.spec.js +273 -0
- package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
- package/package.json +1 -1
- package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
- package/prisma/schema.prisma +35 -7
- package/schema.json +19 -46
- package/scripts/generate-types-from-schema.js +7 -2
- package/scripts/lib/always-present-plugin-args.js +42 -0
- package/lib/public/assets/DeviceMosaicView-ChhHnpFQ.js +0 -2
- package/lib/public/assets/device-explorer-DSpAA_t9.js +0 -279
- package/lib/public/assets/maintenance-settings-BxbA7SkB.js +0 -6
- package/lib/public/assets/recording-group-store-B75435xR.js +0 -26
- package/lib/public/assets/session-detail-page-Bc02YjMm.js +0 -4
- package/lib/public/assets/settings-C1VIluuT.js +0 -6
- package/lib/public/assets/webhook-settings-DAHgnJyZ.js +0 -1
|
@@ -0,0 +1,330 @@
|
|
|
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
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
42
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
43
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
44
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
45
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
46
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
47
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
|
+
};
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.PhoneNetworkRestore = exports.LEFTOVER_MAX_AGE_MS = void 0;
|
|
55
|
+
const typedi_1 = require("typedi");
|
|
56
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
57
|
+
const PortAllocator_1 = require("../PortAllocator");
|
|
58
|
+
const AndroidProxyAdapter_1 = require("../interceptor/AndroidProxyAdapter");
|
|
59
|
+
const adbForPhone_1 = require("./adbForPhone");
|
|
60
|
+
const AndroidRadios_1 = require("./AndroidRadios");
|
|
61
|
+
const PhoneNetworkLedger_1 = require("./PhoneNetworkLedger");
|
|
62
|
+
const phoneNetworkLock_1 = require("./phoneNetworkLock");
|
|
63
|
+
const xenonProxy_1 = require("./xenonProxy");
|
|
64
|
+
/** A record that still can't be undone after this long is dropped: the phone is gone. */
|
|
65
|
+
exports.LEFTOVER_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
66
|
+
/**
|
|
67
|
+
* Puts a phone's network back after a session that changed it: a network
|
|
68
|
+
* profile (`Offline` turns Wi-Fi and mobile data off) or the network
|
|
69
|
+
* interceptor (the phone's global HTTP proxy). Both change the whole phone,
|
|
70
|
+
* not one app, so a phone left that way has no network for the next test.
|
|
71
|
+
*
|
|
72
|
+
* Every way a session ends on this server calls restoreSession, before the
|
|
73
|
+
* phone is released so the next session can't have its own changes undone:
|
|
74
|
+
* the test's delete (SessionLifecycleService.deleteSession), Appium's own end
|
|
75
|
+
* of a session (its new-command timeout, a crashed driver: the plugin's
|
|
76
|
+
* onUnexpectedShutdown), Xenon's idle release (releaseBlockedDevices), a
|
|
77
|
+
* stale heartbeat (OrphanSweeper) and a graceful shutdown. It is keyed by the
|
|
78
|
+
* session id alone, so it works for sessions Xenon keeps no other record of
|
|
79
|
+
* (SESSION_MANAGER holds a local session only with the dashboard or video on).
|
|
80
|
+
*
|
|
81
|
+
* Once per session: a second ending finds the first one's work in progress
|
|
82
|
+
* and waits for it, and a later one finds nothing to do.
|
|
83
|
+
*
|
|
84
|
+
* What a crash forgets, PhoneNetworkLedger remembers. restoreLeftovers undoes
|
|
85
|
+
* it at this server's next start (cleanUpAtBoot) and before the phone's next
|
|
86
|
+
* session here (a phone that was unplugged when its session ended).
|
|
87
|
+
*/
|
|
88
|
+
let PhoneNetworkRestore = class PhoneNetworkRestore {
|
|
89
|
+
constructor() {
|
|
90
|
+
this.logger = logger_1.default.scope('PhoneNetwork');
|
|
91
|
+
this.inflight = new Map();
|
|
92
|
+
this.radios = new AndroidRadios_1.AndroidRadios(adbForPhone_1.adbForPhone);
|
|
93
|
+
this.proxies = new AndroidProxyAdapter_1.AndroidProxyAdapter(adbForPhone_1.adbForPhone);
|
|
94
|
+
}
|
|
95
|
+
/** Undoes what the session changed on its phone's network, once. Never throws. */
|
|
96
|
+
restoreSession(sessionId, why) {
|
|
97
|
+
if (!sessionId)
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
const running = this.inflight.get(sessionId);
|
|
100
|
+
if (running)
|
|
101
|
+
return running;
|
|
102
|
+
const work = this.undoSession(sessionId, why).finally(() => this.inflight.delete(sessionId));
|
|
103
|
+
this.inflight.set(sessionId, work);
|
|
104
|
+
return work;
|
|
105
|
+
}
|
|
106
|
+
/** Every session this server still has network changes for (shutdown). Never throws. */
|
|
107
|
+
restoreAll(why) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
const { net, icp } = yield this.services();
|
|
110
|
+
const ids = new Set([...net.sessionIds(), ...icp.sessionIds(), ...this.inflight.keys()]);
|
|
111
|
+
yield Promise.all([...ids].map((id) => this.restoreSession(id, why)));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Undoes what the ledger says ended sessions left on their phones: one
|
|
116
|
+
* phone's (`udid`) or every phone's. Skips sessions this server still runs.
|
|
117
|
+
* A record that can't be undone now (the phone isn't connected) is kept for
|
|
118
|
+
* next time, for a week. Returns how many records were fully undone.
|
|
119
|
+
* Never throws.
|
|
120
|
+
*/
|
|
121
|
+
restoreLeftovers() {
|
|
122
|
+
return __awaiter(this, arguments, void 0, function* (opts = {}) {
|
|
123
|
+
var _a;
|
|
124
|
+
let done = 0;
|
|
125
|
+
try {
|
|
126
|
+
const ledger = typedi_1.Container.get(PhoneNetworkLedger_1.PhoneNetworkLedger);
|
|
127
|
+
const entries = (yield ledger.list()).filter((e) => !opts.udid || e.udid === opts.udid);
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (yield this.isLive(entry.sessionId))
|
|
130
|
+
continue;
|
|
131
|
+
if (yield this.undoLeftover(entry.sessionId, entry.udid))
|
|
132
|
+
done++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.logger.warn(`Could not put back what earlier sessions left: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
137
|
+
}
|
|
138
|
+
return done;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* At this server's start: first what the ledger holds, then each of this
|
|
143
|
+
* server's Android phones (`udids`) whose proxy is a capture an earlier run
|
|
144
|
+
* left (this machine, one of this server's capture ports, nothing answering
|
|
145
|
+
* there; such a phone has no network). That second check also catches a
|
|
146
|
+
* crash from before the ledger existed. A proxy that points anywhere else,
|
|
147
|
+
* or at a capture that is running, is left alone. Never throws.
|
|
148
|
+
*/
|
|
149
|
+
cleanUpAtBoot(udids) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
var _a;
|
|
152
|
+
yield this.restoreLeftovers({});
|
|
153
|
+
let range;
|
|
154
|
+
try {
|
|
155
|
+
range = typedi_1.Container.get(PortAllocator_1.PortAllocator).rangeOf('proxy');
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
this.logger.warn(`Could not check the phones' proxies: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (const udid of udids)
|
|
162
|
+
yield this.clearDeadCaptureProxy(udid, range);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
undoSession(sessionId, why) {
|
|
166
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
167
|
+
var _a, _b;
|
|
168
|
+
let services;
|
|
169
|
+
try {
|
|
170
|
+
services = yield this.services();
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
this.logger.warn(`Could not put back the network of session ${sessionId}: ${err === null || err === void 0 ? void 0 : err.message}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const { net, icp } = services;
|
|
177
|
+
const profile = net.has(sessionId);
|
|
178
|
+
const capture = icp.isActive(sessionId);
|
|
179
|
+
if (!profile && !capture)
|
|
180
|
+
return;
|
|
181
|
+
this.logger.info(`Putting back the phone network of session ${sessionId} (${why})`);
|
|
182
|
+
if (profile) {
|
|
183
|
+
try {
|
|
184
|
+
yield net.reset(sessionId);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
this.logger.warn(`Network profile reset failed for ${sessionId}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (capture) {
|
|
191
|
+
try {
|
|
192
|
+
yield icp.stop(sessionId);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
this.logger.warn(`Interceptor stop failed for ${sessionId}: ${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
isLive(sessionId) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
if (this.inflight.has(sessionId))
|
|
203
|
+
return true;
|
|
204
|
+
const { net, icp } = yield this.services();
|
|
205
|
+
return net.has(sessionId) || icp.isActive(sessionId);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/** One record, under the phone's lock, read again there: another clean-up may have done it. */
|
|
209
|
+
undoLeftover(sessionId, udid) {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
const ledger = typedi_1.Container.get(PhoneNetworkLedger_1.PhoneNetworkLedger);
|
|
212
|
+
return yield (0, phoneNetworkLock_1.withPhoneNetworkLock)(udid, () => __awaiter(this, void 0, void 0, function* () {
|
|
213
|
+
const change = yield ledger.get(sessionId);
|
|
214
|
+
if (!change)
|
|
215
|
+
return false;
|
|
216
|
+
let undone = true;
|
|
217
|
+
if (change.offline && !(yield this.undoOffline(change, change.offline)))
|
|
218
|
+
undone = false;
|
|
219
|
+
if (change.proxy && !(yield this.undoProxy(change, change.proxy)))
|
|
220
|
+
undone = false;
|
|
221
|
+
if (!undone && Date.now() - change.at > exports.LEFTOVER_MAX_AGE_MS) {
|
|
222
|
+
yield ledger.forget(sessionId);
|
|
223
|
+
this.logger.warn(`Gave up putting back the network of ${udid} (session ${sessionId}): it has not ` +
|
|
224
|
+
'been reachable for a week.');
|
|
225
|
+
}
|
|
226
|
+
return undone;
|
|
227
|
+
}));
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
undoOffline(change, prior) {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
var _a;
|
|
233
|
+
const { udid, sessionId } = change;
|
|
234
|
+
const which = AndroidRadios_1.AndroidRadios.toRestore(prior);
|
|
235
|
+
try {
|
|
236
|
+
yield this.radios.turnOn(udid, which);
|
|
237
|
+
yield typedi_1.Container.get(PhoneNetworkLedger_1.PhoneNetworkLedger).settle(sessionId, 'offline');
|
|
238
|
+
const what = [which.wifi && 'Wi-Fi', which.data && 'mobile data']
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
.join(' and ');
|
|
241
|
+
if (what) {
|
|
242
|
+
this.logger.warn(`Turned ${what} back on on ${udid}: session ${sessionId} took the phone offline and ` +
|
|
243
|
+
'ended without putting it back.');
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
this.logger.warn(`Could not turn the network back on on ${udid} (session ${sessionId}): ` +
|
|
249
|
+
`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}. Trying again at its next session and at the next start.`);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
undoProxy(change, proxy) {
|
|
255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
256
|
+
var _a, _b;
|
|
257
|
+
const { udid, sessionId } = change;
|
|
258
|
+
try {
|
|
259
|
+
const now = (yield this.proxies.readProxy(udid)).trim();
|
|
260
|
+
if (now === proxy.set) {
|
|
261
|
+
yield this.proxies.restoreProxy(udid, proxy.previous);
|
|
262
|
+
if (proxy.reversePort)
|
|
263
|
+
yield this.removeReverse(udid, proxy.reversePort);
|
|
264
|
+
this.logger.warn(`Put back the proxy of ${udid} (${(_a = proxy.previous) !== null && _a !== void 0 ? _a : 'none'}): session ${sessionId} ` +
|
|
265
|
+
`pointed it at ${proxy.set} and ended without putting it back.`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
this.logger.info(`Left the proxy of ${udid} as it is (${now || 'none'}): it changed after session ` +
|
|
269
|
+
`${sessionId} set ${proxy.set}.`);
|
|
270
|
+
}
|
|
271
|
+
yield typedi_1.Container.get(PhoneNetworkLedger_1.PhoneNetworkLedger).settle(sessionId, 'proxy');
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
this.logger.warn(`Could not put back the proxy of ${udid} (session ${sessionId}): ${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}. ` +
|
|
276
|
+
'Trying again at its next session and at the next start.');
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
clearDeadCaptureProxy(udid, range) {
|
|
282
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
283
|
+
var _a;
|
|
284
|
+
try {
|
|
285
|
+
yield (0, phoneNetworkLock_1.withPhoneNetworkLock)(udid, () => __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
const now = yield this.proxies.readProxy(udid);
|
|
287
|
+
const proxy = (0, xenonProxy_1.parseProxySetting)(now);
|
|
288
|
+
if (!proxy || !(yield (0, xenonProxy_1.isDeadCaptureProxy)(now, range)))
|
|
289
|
+
return;
|
|
290
|
+
yield this.proxies.clearProxy(udid);
|
|
291
|
+
if (proxy.host === '127.0.0.1' || proxy.host.toLowerCase() === 'localhost') {
|
|
292
|
+
yield this.removeReverse(udid, proxy.port);
|
|
293
|
+
}
|
|
294
|
+
this.logger.warn(`Cleared the proxy of ${udid} (${now.trim()}): it pointed at this machine's network ` +
|
|
295
|
+
`capture port ${proxy.port}, where nothing answers, so the phone had no network.`);
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
this.logger.debug(`Could not check the proxy of ${udid}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
removeReverse(udid, port) {
|
|
304
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
305
|
+
try {
|
|
306
|
+
yield this.proxies.removeReverse(udid, port);
|
|
307
|
+
}
|
|
308
|
+
catch (_a) {
|
|
309
|
+
// Gone with the adb server or the connection: nothing to remove.
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** Imported lazily: InterceptorService pulls in the socket server. */
|
|
314
|
+
services() {
|
|
315
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
316
|
+
const [{ NetworkConditioningService }, { InterceptorService }] = yield Promise.all([
|
|
317
|
+
Promise.resolve().then(() => __importStar(require('../NetworkConditioningService'))),
|
|
318
|
+
Promise.resolve().then(() => __importStar(require('../InterceptorService'))),
|
|
319
|
+
]);
|
|
320
|
+
return {
|
|
321
|
+
net: typedi_1.Container.get(NetworkConditioningService),
|
|
322
|
+
icp: typedi_1.Container.get(InterceptorService),
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
exports.PhoneNetworkRestore = PhoneNetworkRestore;
|
|
328
|
+
exports.PhoneNetworkRestore = PhoneNetworkRestore = __decorate([
|
|
329
|
+
(0, typedi_1.Service)()
|
|
330
|
+
], PhoneNetworkRestore);
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.adbForPhone = void 0;
|
|
46
|
+
const typedi_1 = require("typedi");
|
|
47
|
+
/**
|
|
48
|
+
* A phone's adb as the rest of Xenon reaches it: appium-adb's, which finds the
|
|
49
|
+
* binary through ANDROID_HOME / ANDROID_SDK_ROOT and runs it without a shell,
|
|
50
|
+
* and which talks to a remote adb server for a phone that has one. Never a
|
|
51
|
+
* bare `adb`: a server started without the SDK on its PATH (Xenon Control, a
|
|
52
|
+
* service) has none.
|
|
53
|
+
*
|
|
54
|
+
* Imported lazily: AndroidDeviceManager pulls in most of the device layer.
|
|
55
|
+
*/
|
|
56
|
+
const adbForPhone = (udid) => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
|
+
const { default: AndroidDeviceManager } = yield Promise.resolve().then(() => __importStar(require('../../device-managers/AndroidDeviceManager')));
|
|
58
|
+
return yield typedi_1.Container.get(AndroidDeviceManager).getAdbForDevice(udid);
|
|
59
|
+
});
|
|
60
|
+
exports.adbForPhone = adbForPhone;
|
|
@@ -0,0 +1,20 @@
|
|
|
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.withPhoneNetworkLock = withPhoneNetworkLock;
|
|
7
|
+
const async_lock_1 = __importDefault(require("async-lock"));
|
|
8
|
+
const lock = new async_lock_1.default({ maxPending: 10000 });
|
|
9
|
+
/**
|
|
10
|
+
* One change at a time to a phone's network settings (Wi-Fi, mobile data, the
|
|
11
|
+
* global proxy): a session's start reading and changing them, its end putting
|
|
12
|
+
* them back, and a clean-up of what an earlier run left. Without it, a
|
|
13
|
+
* clean-up that read a dead proxy could clear the one a new session had just
|
|
14
|
+
* set, or a session could read another's half-undone state as the phone's own.
|
|
15
|
+
*
|
|
16
|
+
* Not reentrant: never call it from inside itself for the same phone.
|
|
17
|
+
*/
|
|
18
|
+
function withPhoneNetworkLock(udid, fn) {
|
|
19
|
+
return lock.acquire(`phone-network:${udid}`, fn);
|
|
20
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.isUnsetProxy = isUnsetProxy;
|
|
16
|
+
exports.parseProxySetting = parseProxySetting;
|
|
17
|
+
exports.localIPv4Addresses = localIPv4Addresses;
|
|
18
|
+
exports.pointsAtThisMachine = pointsAtThisMachine;
|
|
19
|
+
exports.isPortListening = isPortListening;
|
|
20
|
+
exports.isDeadCaptureProxy = isDeadCaptureProxy;
|
|
21
|
+
const net_1 = __importDefault(require("net"));
|
|
22
|
+
const os_1 = __importDefault(require("os"));
|
|
23
|
+
/**
|
|
24
|
+
* Reading a phone's global HTTP proxy (`settings get global http_proxy`), and
|
|
25
|
+
* telling one Xenon's network capture left behind from one somebody set on
|
|
26
|
+
* purpose.
|
|
27
|
+
*/
|
|
28
|
+
/** Android's ways of saying "no proxy": unset (`null`), empty, or `:0`. */
|
|
29
|
+
function isUnsetProxy(value) {
|
|
30
|
+
const v = (value !== null && value !== void 0 ? value : '').trim();
|
|
31
|
+
return v === '' || v === 'null' || v === ':0';
|
|
32
|
+
}
|
|
33
|
+
/** `host:port`, or null for no proxy or anything else. */
|
|
34
|
+
function parseProxySetting(value) {
|
|
35
|
+
if (isUnsetProxy(value))
|
|
36
|
+
return null;
|
|
37
|
+
const v = value.trim();
|
|
38
|
+
const colon = v.lastIndexOf(':');
|
|
39
|
+
if (colon <= 0)
|
|
40
|
+
return null;
|
|
41
|
+
const host = v.slice(0, colon);
|
|
42
|
+
const portText = v.slice(colon + 1);
|
|
43
|
+
if (!/^\d+$/.test(portText))
|
|
44
|
+
return null;
|
|
45
|
+
const port = Number(portText);
|
|
46
|
+
if (port < 1 || port > 65535)
|
|
47
|
+
return null;
|
|
48
|
+
return { host, port };
|
|
49
|
+
}
|
|
50
|
+
/** This machine's own IPv4 addresses, as a phone on its network reaches it. */
|
|
51
|
+
function localIPv4Addresses() {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const infos of Object.values(os_1.default.networkInterfaces())) {
|
|
54
|
+
for (const info of infos !== null && infos !== void 0 ? infos : []) {
|
|
55
|
+
if (info.family === 'IPv4' && !info.internal)
|
|
56
|
+
out.push(info.address);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether a phone that uses `host` as its proxy reaches this machine: through
|
|
63
|
+
* `adb reverse` (127.0.0.1, the phone itself), an emulator's alias for its
|
|
64
|
+
* host (10.0.2.2), or this machine's own address (the interceptor's LAN
|
|
65
|
+
* fallback). These are the only hosts the interceptor ever sets.
|
|
66
|
+
*/
|
|
67
|
+
function pointsAtThisMachine(host, ownAddresses) {
|
|
68
|
+
const h = host.trim().toLowerCase();
|
|
69
|
+
return h === '127.0.0.1' || h === 'localhost' || h === '10.0.2.2' || ownAddresses.includes(h);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether something on this machine accepts a TCP connection on `port`.
|
|
73
|
+
* Anything but a refusal counts as yes (a timeout too): only a port known to be
|
|
74
|
+
* dead may have a proxy that points at it cleared.
|
|
75
|
+
*/
|
|
76
|
+
function isPortListening(port, timeoutMs = 1000) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const socket = net_1.default.connect({ host: '127.0.0.1', port });
|
|
79
|
+
const done = (answer) => {
|
|
80
|
+
socket.removeAllListeners();
|
|
81
|
+
socket.destroy();
|
|
82
|
+
resolve(answer);
|
|
83
|
+
};
|
|
84
|
+
socket.setTimeout(timeoutMs, () => done(true));
|
|
85
|
+
socket.once('connect', () => done(true));
|
|
86
|
+
socket.once('error', (err) => done(err.code !== 'ECONNREFUSED'));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether `value` is a proxy Xenon's network capture set and nobody serves any
|
|
91
|
+
* more: it points at this machine, on one of this server's capture ports, and
|
|
92
|
+
* nothing answers there. A phone with such a proxy has no network at all. A
|
|
93
|
+
* proxy that points anywhere else, or at a port that answers (a capture that
|
|
94
|
+
* is running, maybe another Xenon's on this machine), is never one.
|
|
95
|
+
*/
|
|
96
|
+
function isDeadCaptureProxy(value_1, captureRange_1) {
|
|
97
|
+
return __awaiter(this, arguments, void 0, function* (value, captureRange, probe = isPortListening) {
|
|
98
|
+
const proxy = parseProxySetting(value);
|
|
99
|
+
if (!proxy)
|
|
100
|
+
return false;
|
|
101
|
+
if (!pointsAtThisMachine(proxy.host, localIPv4Addresses()))
|
|
102
|
+
return false;
|
|
103
|
+
if (proxy.port < captureRange[0] || proxy.port > captureRange[1])
|
|
104
|
+
return false;
|
|
105
|
+
return !(yield probe(proxy.port));
|
|
106
|
+
});
|
|
107
|
+
}
|