@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
|
@@ -70,6 +70,7 @@ const ResilioTreeHealingProvider_1 = require("./ResilioTreeHealingProvider");
|
|
|
70
70
|
const HealingMetrics_1 = require("./HealingMetrics");
|
|
71
71
|
const attributes_1 = require("../telemetry/attributes");
|
|
72
72
|
const xenonOptions_1 = require("../session/xenonOptions");
|
|
73
|
+
const screenScale_1 = require("../omni-vision/screenScale");
|
|
73
74
|
// §2.7 healing-tier capability gate. The tier numbering here is the
|
|
74
75
|
// externally-facing capability contract (xe:options.healingTiers),
|
|
75
76
|
// which is array-position based: providers[i] is tier i+1 (1=Resilio,
|
|
@@ -158,7 +159,10 @@ let HealingOrchestrator = class HealingOrchestrator {
|
|
|
158
159
|
span.addEvent('tier_started', { tier: provider.name });
|
|
159
160
|
try {
|
|
160
161
|
this.logger.info(`Attempting Tier ${provider.tier}: ${provider.name}...`);
|
|
161
|
-
|
|
162
|
+
let result = yield provider.heal(context);
|
|
163
|
+
if (result === null || result === void 0 ? void 0 : result.rect) {
|
|
164
|
+
result = yield this.rectInDriverCoordinates(result, result.rect, context, provider);
|
|
165
|
+
}
|
|
162
166
|
HealingMetrics_1.HEALING_METRICS.record(provider.tier, provider.name, result ? 'success' : 'failure', Date.now() - tierStart);
|
|
163
167
|
if (result) {
|
|
164
168
|
this.logger.info(`✨ Provider ${provider.name} found a match! Confidence: ${(result.confidence * 100).toFixed(0)}%`);
|
|
@@ -267,6 +271,33 @@ let HealingOrchestrator = class HealingOrchestrator {
|
|
|
267
271
|
return null;
|
|
268
272
|
});
|
|
269
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* The OCR and Visual AI tiers find the element in the screenshot, so their
|
|
276
|
+
* `rect` is in its pixels. The interceptor taps it, and asks iOS for the
|
|
277
|
+
* element there, in the driver's coordinates (points on iOS), so it is
|
|
278
|
+
* converted here. If that can't be worked out on iOS, a virtual element
|
|
279
|
+
* (only a position) is no use and the tier counts as failed; a real element
|
|
280
|
+
* the tier resolved keeps its id and loses only the rect.
|
|
281
|
+
*/
|
|
282
|
+
rectInDriverCoordinates(result, rect, context, provider) {
|
|
283
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
var _a, _b;
|
|
285
|
+
try {
|
|
286
|
+
const scale = yield (0, screenScale_1.screenScaleOf)(context.driver, (_a = context.screenshotBase64) !== null && _a !== void 0 ? _a : '');
|
|
287
|
+
return Object.assign(Object.assign({}, result), { rect: (0, screenScale_1.toDriverRect)(rect, scale) });
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const reason = (_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err;
|
|
291
|
+
if (!result.id.startsWith('healed_')) {
|
|
292
|
+
this.logger.warn(`${provider.name}: the element's position is unknown (${reason}).`);
|
|
293
|
+
return Object.assign(Object.assign({}, result), { rect: undefined });
|
|
294
|
+
}
|
|
295
|
+
this.logger.warn(`${provider.name} found the element in the screenshot, but not where it is on the ` +
|
|
296
|
+
`screen (${reason}); trying the next tier.`);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
270
301
|
};
|
|
271
302
|
exports.HealingOrchestrator = HealingOrchestrator;
|
|
272
303
|
exports.HealingOrchestrator = HealingOrchestrator = __decorate([
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.AndroidProxyAdapter = void 0;
|
|
13
|
+
const xenonProxy_1 = require("../network/xenonProxy");
|
|
13
14
|
class AndroidProxyAdapter {
|
|
14
15
|
constructor(getAdb) {
|
|
15
16
|
this.getAdb = getAdb;
|
|
@@ -38,6 +39,23 @@ class AndroidProxyAdapter {
|
|
|
38
39
|
yield adb.adbExec(['-s', udid, 'shell', 'settings', 'put', 'global', 'http_proxy', ':0']);
|
|
39
40
|
});
|
|
40
41
|
}
|
|
42
|
+
/** The phone's global HTTP proxy as stored: `host:port`, or `null` / `:0` / '' for none. */
|
|
43
|
+
readProxy(udid) {
|
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
const adb = yield this.getAdb(udid);
|
|
46
|
+
const out = yield adb.adbExec(['-s', udid, 'shell', 'settings', 'get', 'global', 'http_proxy']);
|
|
47
|
+
return String(out !== null && out !== void 0 ? out : '').trim();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Puts `previous` back as the phone's proxy, or clears it when there was none. */
|
|
51
|
+
restoreProxy(udid, previous) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
if (previous === null || (0, xenonProxy_1.isUnsetProxy)(previous))
|
|
54
|
+
return this.clearProxy(udid);
|
|
55
|
+
const adb = yield this.getAdb(udid);
|
|
56
|
+
yield adb.adbExec(['-s', udid, 'shell', 'settings', 'put', 'global', 'http_proxy', previous]);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
41
59
|
// Forwards a port on the device back to a port on the host over the adb transport
|
|
42
60
|
// (USB or wireless adb). The device sees `127.0.0.1:devicePort` and that traffic
|
|
43
61
|
// reaches the host's `hostPort`. This is the standard way to make a host-side
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveInterceptorOptions = resolveInterceptorOptions;
|
|
4
|
+
const XenonCapabilityManager_1 = require("../../XenonCapabilityManager");
|
|
5
|
+
/**
|
|
6
|
+
* The network capture a session gets: its own capability (any of the shapes
|
|
7
|
+
* getXenonCapabilities reads) wins field by field, and the server's
|
|
8
|
+
* `interceptor` option fills in what the session leaves unset.
|
|
9
|
+
*
|
|
10
|
+
* - `enabled`: the session's, when it says true or false; else the server's;
|
|
11
|
+
* else off.
|
|
12
|
+
* - `bufferSize`, `captureBodies`: the session's, else the server's, else the
|
|
13
|
+
* service's defaults (1000, true).
|
|
14
|
+
* - `mocks`, `includeHosts`, `excludeHosts`: the session's only.
|
|
15
|
+
*
|
|
16
|
+
* `sessionCaps` is getXenonCapabilities' output, where a field the session
|
|
17
|
+
* didn't set is `undefined`.
|
|
18
|
+
*/
|
|
19
|
+
function resolveInterceptorOptions(sessionCaps, server) {
|
|
20
|
+
var _a;
|
|
21
|
+
const own = (key) => sessionCaps[key];
|
|
22
|
+
const enabled = own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_ENABLED);
|
|
23
|
+
const bufferSize = own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_BUFFER_SIZE);
|
|
24
|
+
const captureBodies = own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_CAPTURE_BODIES);
|
|
25
|
+
return {
|
|
26
|
+
enabled: typeof enabled === 'boolean' ? enabled : (server === null || server === void 0 ? void 0 : server.enabled) === true,
|
|
27
|
+
bufferSize: (_a = positive(bufferSize)) !== null && _a !== void 0 ? _a : positive(server === null || server === void 0 ? void 0 : server.bufferSize),
|
|
28
|
+
captureBodies: typeof captureBodies === 'boolean'
|
|
29
|
+
? captureBodies
|
|
30
|
+
: typeof (server === null || server === void 0 ? void 0 : server.captureBodies) === 'boolean'
|
|
31
|
+
? server.captureBodies
|
|
32
|
+
: true,
|
|
33
|
+
mocks: listOr(own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_MOCKS)),
|
|
34
|
+
includeHosts: listOr(own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_INCLUDE_HOSTS)),
|
|
35
|
+
excludeHosts: listOr(own(XenonCapabilityManager_1.XENON_CAPABILITIES.INTERCEPTOR_EXCLUDE_HOSTS)),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function positive(value) {
|
|
39
|
+
const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
|
|
40
|
+
return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : undefined;
|
|
41
|
+
}
|
|
42
|
+
function listOr(value) {
|
|
43
|
+
return Array.isArray(value) ? value : [];
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.leaseKey = void 0;
|
|
13
|
+
exports.activeLeasesByDevice = activeLeasesByDevice;
|
|
14
|
+
exports.activeLeaseOn = activeLeaseOn;
|
|
15
|
+
const prisma_1 = require("../../prisma");
|
|
16
|
+
const leaseKey = (udid, host) => `${udid}@${host}`;
|
|
17
|
+
exports.leaseKey = leaseKey;
|
|
18
|
+
/** Every live lease, keyed by `leaseKey(udid, host)`. */
|
|
19
|
+
function activeLeasesByDevice() {
|
|
20
|
+
return __awaiter(this, arguments, void 0, function* (db = prisma_1.prisma, now = Date.now()) {
|
|
21
|
+
const rows = yield db.lease.findMany({
|
|
22
|
+
where: { status: 'active', expiresAt: { gte: now } },
|
|
23
|
+
select: { id: true, deviceUdid: true, deviceHost: true, actorId: true },
|
|
24
|
+
});
|
|
25
|
+
return new Map(rows.map((l) => [(0, exports.leaseKey)(l.deviceUdid, l.deviceHost), l]));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** The live lease on one phone, or null. */
|
|
29
|
+
function activeLeaseOn(udid_1, host_1) {
|
|
30
|
+
return __awaiter(this, arguments, void 0, function* (udid, host, db = prisma_1.prisma, now = Date.now()) {
|
|
31
|
+
var _a;
|
|
32
|
+
return ((_a = (yield db.lease.findFirst({
|
|
33
|
+
where: { deviceUdid: udid, deviceHost: host, status: 'active', expiresAt: { gte: now } },
|
|
34
|
+
select: { id: true, deviceUdid: true, deviceHost: true, actorId: true },
|
|
35
|
+
}))) !== null && _a !== void 0 ? _a : null);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AndroidRadios = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* A phone's Wi-Fi and mobile data, through the resolved adb (`adbExec`: no
|
|
15
|
+
* shell, no bare `adb`).
|
|
16
|
+
*/
|
|
17
|
+
class AndroidRadios {
|
|
18
|
+
constructor(getAdb) {
|
|
19
|
+
this.getAdb = getAdb;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `wifi_on` is 1 or 2 when Wi-Fi is on (2: on in spite of airplane mode),
|
|
23
|
+
* 0 or 3 when it is off. `mobile_data` is 1 or 0. Anything else, or a read
|
|
24
|
+
* that fails, is null: "don't know".
|
|
25
|
+
*/
|
|
26
|
+
read(udid) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const adb = yield this.getAdb(udid);
|
|
29
|
+
const get = (name) => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
const out = yield adb.adbExec(['-s', udid, 'shell', 'settings', 'get', 'global', name]);
|
|
32
|
+
return String(out !== null && out !== void 0 ? out : '').trim();
|
|
33
|
+
}
|
|
34
|
+
catch (_a) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const wifi = yield get('wifi_on');
|
|
39
|
+
const data = yield get('mobile_data');
|
|
40
|
+
return {
|
|
41
|
+
wifiOn: wifi === '1' || wifi === '2' ? true : wifi === '0' || wifi === '3' ? false : null,
|
|
42
|
+
dataOn: data === '1' ? true : data === '0' ? false : null,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Mobile data, then Wi-Fi, off. */
|
|
47
|
+
turnOff(udid) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const adb = yield this.getAdb(udid);
|
|
50
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'data', 'disable']);
|
|
51
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'wifi', 'disable']);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Turns on the ones asked for; leaves the others as they are. */
|
|
55
|
+
turnOn(udid, which) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
if (!which.data && !which.wifi)
|
|
58
|
+
return;
|
|
59
|
+
const adb = yield this.getAdb(udid);
|
|
60
|
+
if (which.data)
|
|
61
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'data', 'enable']);
|
|
62
|
+
if (which.wifi)
|
|
63
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'wifi', 'enable']);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** What to turn back on after Offline: what was on, and what nobody knew about. */
|
|
67
|
+
static toRestore(prior) {
|
|
68
|
+
return { data: prior.dataOn !== false, wifi: prior.wifiOn !== false };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.AndroidRadios = AndroidRadios;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.PhoneNetworkLedger = void 0;
|
|
22
|
+
const typedi_1 = require("typedi");
|
|
23
|
+
const async_lock_1 = __importDefault(require("async-lock"));
|
|
24
|
+
const prisma_1 = require("../../prisma");
|
|
25
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
26
|
+
const PREFIX = 'phone-network:';
|
|
27
|
+
/**
|
|
28
|
+
* Xenon's record of the network changes it made to its phones, kept until
|
|
29
|
+
* each is put back. In memory a session's changes are undone when it ends; a
|
|
30
|
+
* crash or a kill -9 forgets them, and the phone stays offline or pointed at
|
|
31
|
+
* a proxy nobody serves. At its next start the server reads this record and
|
|
32
|
+
* puts those phones back (PhoneNetworkRestore).
|
|
33
|
+
*
|
|
34
|
+
* Kept in this server's own database, which is what makes it this server's:
|
|
35
|
+
* a hub and a node on one machine see the same Android phones, but each has
|
|
36
|
+
* its own database. It has no table of its own: WebConfig is the database's
|
|
37
|
+
* key-value table (MetricsService keeps its counters there), and a new table
|
|
38
|
+
* would mean a migration and a regenerated client for a handful of rows. One
|
|
39
|
+
* row per session, `phone-network:<sessionId>`, holding the JSON above.
|
|
40
|
+
*
|
|
41
|
+
* A failed write is logged and never fails a session: the change is still
|
|
42
|
+
* made, and undone when the session ends; only a crash before then would
|
|
43
|
+
* leave it.
|
|
44
|
+
*/
|
|
45
|
+
let PhoneNetworkLedger = class PhoneNetworkLedger {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.logger = logger_1.default.scope('PhoneNetwork');
|
|
48
|
+
this.lock = new async_lock_1.default();
|
|
49
|
+
}
|
|
50
|
+
/** Adds `part` to the session's record, creating the record if needed. */
|
|
51
|
+
note(sessionId, udid, part) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
yield this.guard(`record a network change for ${udid} (session ${sessionId})`, () => this.lock.acquire(sessionId, () => __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const current = yield this.read(sessionId);
|
|
55
|
+
const next = Object.assign(Object.assign({}, (current !== null && current !== void 0 ? current : { sessionId, udid, at: Date.now() })), part);
|
|
56
|
+
yield this.write(next);
|
|
57
|
+
})));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** The part was put back: drops it, and the record once nothing is left. */
|
|
61
|
+
settle(sessionId, part) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
yield this.guard(`settle the ${part} change of session ${sessionId}`, () => this.lock.acquire(sessionId, () => __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const current = yield this.read(sessionId);
|
|
65
|
+
if (!current)
|
|
66
|
+
return;
|
|
67
|
+
const next = Object.assign({}, current);
|
|
68
|
+
delete next[part];
|
|
69
|
+
if (next.offline || next.proxy)
|
|
70
|
+
yield this.write(next);
|
|
71
|
+
else
|
|
72
|
+
yield this.remove(sessionId);
|
|
73
|
+
})));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Drops the session's record, whatever it holds. */
|
|
77
|
+
forget(sessionId) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
yield this.guard(`forget the network changes of session ${sessionId}`, () => this.lock.acquire(sessionId, () => this.remove(sessionId)));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** The session's record, or null. */
|
|
83
|
+
get(sessionId) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
var _a;
|
|
86
|
+
try {
|
|
87
|
+
return yield this.read(sessionId);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
this.logger.warn(`Could not read the phone network record: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Every record, oldest first. A row that isn't a record is dropped. */
|
|
96
|
+
list() {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
var _a;
|
|
99
|
+
let rows = [];
|
|
100
|
+
try {
|
|
101
|
+
rows = yield prisma_1.prisma.webConfig.findMany({
|
|
102
|
+
where: { name: { startsWith: PREFIX } },
|
|
103
|
+
select: { name: true, value: true },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
this.logger.warn(`Could not read the phone network record: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const row of rows) {
|
|
112
|
+
const change = parseChange(row.value);
|
|
113
|
+
if (change)
|
|
114
|
+
out.push(change);
|
|
115
|
+
else
|
|
116
|
+
yield this.remove(row.name.slice(PREFIX.length)).catch(() => undefined);
|
|
117
|
+
}
|
|
118
|
+
return out.sort((a, b) => a.at - b.at);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
read(sessionId) {
|
|
122
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
const row = yield prisma_1.prisma.webConfig.findUnique({ where: { name: PREFIX + sessionId } });
|
|
124
|
+
return row ? parseChange(row.value) : null;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
write(change) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
const name = PREFIX + change.sessionId;
|
|
130
|
+
const value = JSON.stringify(change);
|
|
131
|
+
yield prisma_1.prisma.webConfig.upsert({
|
|
132
|
+
where: { name },
|
|
133
|
+
update: { value },
|
|
134
|
+
create: { id: name, name, value },
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
remove(sessionId) {
|
|
139
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
yield prisma_1.prisma.webConfig.deleteMany({ where: { name: PREFIX + sessionId } });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
guard(what, fn) {
|
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
var _a;
|
|
146
|
+
try {
|
|
147
|
+
yield fn();
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
this.logger.warn(`Could not ${what}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
exports.PhoneNetworkLedger = PhoneNetworkLedger;
|
|
156
|
+
exports.PhoneNetworkLedger = PhoneNetworkLedger = __decorate([
|
|
157
|
+
(0, typedi_1.Service)()
|
|
158
|
+
], PhoneNetworkLedger);
|
|
159
|
+
function parseChange(value) {
|
|
160
|
+
try {
|
|
161
|
+
const v = JSON.parse(value);
|
|
162
|
+
if (!v || typeof v.sessionId !== 'string' || typeof v.udid !== 'string')
|
|
163
|
+
return null;
|
|
164
|
+
if (typeof v.at !== 'number')
|
|
165
|
+
return null;
|
|
166
|
+
const change = { sessionId: v.sessionId, udid: v.udid, at: v.at };
|
|
167
|
+
if (v.offline && typeof v.offline === 'object') {
|
|
168
|
+
change.offline = {
|
|
169
|
+
wifiOn: boolOrNull(v.offline.wifiOn),
|
|
170
|
+
dataOn: boolOrNull(v.offline.dataOn),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (v.proxy && typeof v.proxy.set === 'string') {
|
|
174
|
+
change.proxy = Object.assign({ set: v.proxy.set, previous: typeof v.proxy.previous === 'string' ? v.proxy.previous : null }, (typeof v.proxy.reversePort === 'number' ? { reversePort: v.proxy.reversePort } : {}));
|
|
175
|
+
}
|
|
176
|
+
return change.offline || change.proxy ? change : null;
|
|
177
|
+
}
|
|
178
|
+
catch (_a) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function boolOrNull(v) {
|
|
183
|
+
return typeof v === 'boolean' ? v : null;
|
|
184
|
+
}
|