@xenon-device-management/xenon 2.15.0 → 2.16.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 +38 -26
- package/lib/package.json +2 -1
- package/lib/public/assets/{AnnotationOverlay-Btq3dZRI.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-C9OoAgg_.js → ApiKeyGate-GmckX_7r.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-DBC_xczB.js → DeviceMosaicView-DqODzkyo.js} +1 -1
- package/lib/public/assets/{EmptyState-DqWU2-vM.js → EmptyState-7hETKEWb.js} +1 -1
- package/lib/public/assets/{FieldGroup-CveQ3xrA.js → FieldGroup-GOgssFEP.js} +1 -1
- package/lib/public/assets/{FilterMenu-BfKDSfHz.js → FilterMenu-Yvf6RGMd.js} +1 -1
- package/lib/public/assets/{Menu-CtdCybzA.js → Menu-KrPnfdsf.js} +1 -1
- package/lib/public/assets/{Modal-Bsvj1tm2.js → Modal-C6WjZFeI.js} +1 -1
- package/lib/public/assets/{RecordingPage-CRofOiyx.js → RecordingPage-CSRIhZEh.js} +1 -1
- package/lib/public/assets/{RecordingsPage-jNQHb2IR.js → RecordingsPage-CB8ST3nE.js} +1 -1
- package/lib/public/assets/{SegmentedControl-C8HtTha-.js → SegmentedControl-C9o90KId.js} +1 -1
- package/lib/public/assets/{SettingCard-D8K1LZNo.js → SettingCard-CPm3w9l2.js} +1 -1
- package/lib/public/assets/{Table-zNTX92Pn.js → Table-DwIgERRt.js} +1 -1
- package/lib/public/assets/{activity-CJ8LwZq9.js → activity-DrLbHifn.js} +1 -1
- package/lib/public/assets/{ai-settings-CgxQZqrc.js → ai-settings-DU_Ls06P.js} +1 -1
- package/lib/public/assets/{api-keys-ECpo-zTo.js → api-keys-CWYdEzJ_.js} +1 -1
- package/lib/public/assets/{apps-BjGymyf3.js → apps-BqNVdln7.js} +1 -1
- package/lib/public/assets/{arrow-left-BWfCbeHP.js → arrow-left-BtB0_Jg_.js} +1 -1
- package/lib/public/assets/{arrow-right-DC7UGf_n.js → arrow-right-C8NhdtyN.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BOjwybDk.js → arrow-up-right-gMg0ffvv.js} +1 -1
- package/lib/public/assets/{auth-shell-CxMTo9aH.js → auth-shell-CiYZ3N4M.js} +2 -2
- package/lib/public/assets/{builds-page-DOCm4HkA.js → builds-page-EaaqDmLN.js} +1 -1
- package/lib/public/assets/{button-Cg08-EMT.js → button-DDau4Axv.js} +1 -1
- package/lib/public/assets/{calendar-CA1ikLJs.js → calendar-DQ83B1ed.js} +1 -1
- package/lib/public/assets/{check-BMTaH72W.js → check-K1ovTQam.js} +1 -1
- package/lib/public/assets/{chevron-right-B2QOv9Nx.js → chevron-right-Biq00pMt.js} +1 -1
- package/lib/public/assets/{circle-check-CYXBoUqT.js → circle-check-CBv88cH7.js} +1 -1
- package/lib/public/assets/{circle-x-DTwnSAnl.js → circle-x-Bmltailn.js} +1 -1
- package/lib/public/assets/{clock-C8_HViQP.js → clock-BPrld-3h.js} +1 -1
- package/lib/public/assets/{copy-Be7LNMXV.js → copy-t0QB14JI.js} +1 -1
- package/lib/public/assets/{device-explorer-BOqtfGXz.js → device-explorer-BgGZ8AvV.js} +1 -1
- package/lib/public/assets/{download-B56lwxWP.js → download-BroHxNOh.js} +1 -1
- package/lib/public/assets/{forgot-password-CCU6bzLn.js → forgot-password-Bjmf49i6.js} +1 -1
- package/lib/public/assets/{h264Stream-C-OtmIuk.js → h264Stream-DeD__6wa.js} +1 -1
- package/lib/public/assets/{index-CfxsUslp.js → index-72oF89LJ.js} +1 -1
- package/lib/public/assets/{index-B9KQ6LsY.js → index-B0JeuZbg.js} +1 -1
- package/lib/public/assets/index-B0gQkEg0.js +219 -0
- package/lib/public/assets/{input-CZHFjbSg.js → input-DIc4MSC7.js} +1 -1
- package/lib/public/assets/{line-chart-Gl_Dz3X_.js → line-chart-BcWm2vW8.js} +1 -1
- package/lib/public/assets/{list-checks-CCgjeyGY.js → list-checks-B06IJJad.js} +1 -1
- package/lib/public/assets/{lock-CkOwgDcx.js → lock-pVLvnN30.js} +1 -1
- package/lib/public/assets/{login-D5cc4w7M.js → login-CN77vy--.js} +1 -1
- package/lib/public/assets/{maintenance-settings-t7qr3fvX.js → maintenance-settings-CSdzQSkY.js} +1 -1
- package/lib/public/assets/{monitor-DEJixKQO.js → monitor-BtrN5IkI.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CtOrk4M8.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
- package/lib/public/assets/{network-Dii9qdvk.js → network-BSfA8ZxG.js} +1 -1
- package/lib/public/assets/{overview-rolAACrO.js → overview-BfTebCK5.js} +1 -1
- package/lib/public/assets/{page-header-COQZxJfR.js → page-header-D8KUX6Qg.js} +1 -1
- package/lib/public/assets/{play-DrN1Se_R.js → play-Cvc8xzic.js} +1 -1
- package/lib/public/assets/{plus-5aq-9uRx.js → plus-C9Zj84UF.js} +1 -1
- package/lib/public/assets/{profile-page-6i5kY6kF.js → profile-page-Bi27STgs.js} +1 -1
- package/lib/public/assets/{reset-password-sp2IBrc7.js → reset-password-CPsOuwSu.js} +1 -1
- package/lib/public/assets/{runbook-page-DJDjeopU.js → runbook-page-DEQ-DBOX.js} +153 -102
- package/lib/public/assets/{select-Bpyn7rWO.js → select-BWpnhWXj.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BH74vmKv.js → selector-detail-redirect-BefSCSpe.js} +1 -1
- package/lib/public/assets/{selector-health-page-Cw_g218H.js → selector-health-page-MSk96Zz7.js} +1 -1
- package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
- package/lib/public/assets/{settings-C3uyGhyc.js → settings-V6x-cXwy.js} +1 -1
- package/lib/public/assets/{stat-tile-xVqAKIlI.js → stat-tile-Duex-GZD.js} +1 -1
- package/lib/public/assets/{tablet-BxUScAeO.js → tablet-BBcoqBmL.js} +1 -1
- package/lib/public/assets/{teams-B5ljGkY2.js → teams-ts8p5rEh.js} +1 -1
- package/lib/public/assets/{trash-2-B701i8sy.js → trash-2-CZ2ew6AZ.js} +1 -1
- package/lib/public/assets/{upload-C2gr44RS.js → upload-Dv99y_le.js} +1 -1
- package/lib/public/assets/{use-builds-data-NeQOFEoy.js → use-builds-data-DrYC8JCF.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DEzJq5ef.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
- package/lib/public/assets/{users-D_vv_yz4.js → users-D7tKMESH.js} +1 -1
- package/lib/public/assets/{users-MY6eNe6s.js → users-gJ5Y01yo.js} +1 -1
- package/lib/public/assets/{video-off-OK1lNZQn.js → video-off-CqZVuZGs.js} +1 -1
- package/lib/public/assets/{webhook-settings-BU-C9lBf.js → webhook-settings-DI5DSW99.js} +1 -1
- package/lib/public/assets/{zap-DO9ry1iz.js → zap-ClcHFxfo.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +11 -7
- package/lib/src/InternalHttpClient.js +27 -5
- package/lib/src/XenonCapabilityManager.js +10 -3
- package/lib/src/app/openapi/sessions.yaml +7 -5
- package/lib/src/app/routers/auth.js +2 -1
- package/lib/src/app/routers/users.js +2 -1
- package/lib/src/app/ws/nodeSocketRelay.js +77 -11
- package/lib/src/dashboard/commandLogFields.js +42 -0
- package/lib/src/dashboard/event-manager.js +26 -154
- package/lib/src/dashboard/services/failure-analysis-service.js +8 -86
- package/lib/src/dashboard/services/failureCategories.js +264 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
- package/lib/src/device-managers/IOSDeviceManager.js +10 -0
- package/lib/src/device-utils.js +14 -6
- package/lib/src/gateway/forwardToNode.js +4 -11
- package/lib/src/gateway/hubSessionToken.js +27 -4
- package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
- package/lib/src/helpers/outboundProxy.js +219 -0
- package/lib/src/index.js +21 -0
- package/lib/src/interceptors/CommandInterceptor.js +13 -15
- package/lib/src/interceptors/xenonScripts.js +28 -7
- package/lib/src/logger.js +3 -1
- package/lib/src/plugin.js +8 -3
- package/lib/src/scripts/database-check.js +3 -3
- package/lib/src/scripts/initialize-database.js +35 -33
- package/lib/src/scripts/run-migrations.js +1079 -27
- package/lib/src/services/ApiKeyService.js +16 -5
- package/lib/src/services/InterceptorService.js +6 -2
- package/lib/src/services/OrphanSweeper.js +6 -0
- package/lib/src/services/SelectorStateService.js +11 -7
- package/lib/src/services/SelectorVerificationJob.js +6 -2
- package/lib/src/services/SessionLifecycleService.js +32 -18
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +361 -42
- package/lib/src/services/TeamService.js +5 -1
- package/lib/src/services/TracingService.js +171 -60
- package/lib/src/services/UserService.js +14 -3
- package/lib/src/services/UserSessionService.js +16 -4
- package/lib/src/services/healing/HealingOrchestrator.js +3 -1
- package/lib/src/services/identity/identityChanges.js +41 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +97 -10
- package/lib/src/services/logcat/deviceLogBook.js +5 -4
- package/lib/src/services/logcat/iosDriverLog.js +178 -0
- package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
- package/lib/src/services/token/JwtKeyService.js +8 -2
- package/lib/src/services/token/mcpTokenTtl.js +27 -0
- package/lib/src/sessions/LocalSession.js +29 -1
- package/lib/src/sessions/RemoteSession.js +12 -2
- package/lib/src/sessions/SessionManager.js +5 -8
- package/lib/test/helpers/driver-log.js +38 -0
- package/lib/test/helpers/fake-http-proxy.js +106 -0
- package/lib/test/helpers/otlp-probe.js +107 -0
- package/lib/test/integration/androidDevices.spec.js +1 -1
- package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
- package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
- package/lib/test/integration/selector-state-flow.spec.js +5 -5
- package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
- package/lib/test/unit/android-adb-retry.spec.js +112 -0
- package/lib/test/unit/command-log-network-capture.spec.js +191 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +59 -11
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/database-check.spec.js +1 -0
- package/lib/test/unit/device-utils.spec.js +13 -4
- package/lib/test/unit/event-log-session-data.spec.js +11 -4
- package/lib/test/unit/failure-analysis.spec.js +22 -8
- package/lib/test/unit/failure-categories.spec.js +24 -22
- package/lib/test/unit/failure-categorization.spec.js +393 -0
- package/lib/test/unit/healing-tier-gate.spec.js +3 -2
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +72 -1
- package/lib/test/unit/ios-driver-log.spec.js +168 -0
- package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
- package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
- package/lib/test/unit/logcat-ws.spec.js +78 -37
- package/lib/test/unit/logger-json-levels.spec.js +44 -0
- package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
- package/lib/test/unit/observability-example-loki.spec.js +25 -0
- package/lib/test/unit/openapi-for-site.spec.js +80 -0
- package/lib/test/unit/outbound-proxy.spec.js +320 -0
- package/lib/test/unit/port-exhaustion.spec.js +2 -1
- package/lib/test/unit/proxy-option.spec.js +358 -0
- package/lib/test/unit/run-migrations-database.spec.js +807 -0
- package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
- package/lib/test/unit/run-migrations.test.js +864 -0
- package/lib/test/unit/selector-health-actions.spec.js +3 -1
- package/lib/test/unit/selector-state-service.spec.js +72 -23
- package/lib/test/unit/selector-verification-clean-build.spec.js +1 -1
- package/lib/test/unit/selector-verification-job.spec.js +15 -6
- package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
- package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
- package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
- package/lib/test/unit/session-device-logs.spec.js +11 -2
- package/lib/test/unit/session-end-failure-analysis.spec.js +5 -5
- package/lib/test/unit/session-log-indexes.spec.js +1 -1
- package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
- package/lib/test/unit/tracing-export-routing.spec.js +137 -0
- package/lib/test/unit/tracing-session-spans.spec.js +337 -0
- package/lib/test/unit/unexpected-shutdown-reason.spec.js +2 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/schema.json +11 -7
- package/scripts/dev/readme-screenshots.js +276 -23
- package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
- package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
- package/scripts/dev/screenshot-fixtures/session.js +242 -0
- package/scripts/export-openapi.js +32 -0
- package/scripts/lib/openapi-for-site.js +79 -0
- package/lib/public/assets/index-BOxe43p1.js +0 -219
- package/lib/public/assets/session-detail-page-AZJE6XXE.js +0 -4
- package/lib/src/scripts/prepare-prisma.js +0 -89
|
@@ -23,6 +23,7 @@ const typedi_1 = require("typedi");
|
|
|
23
23
|
const crypto_1 = __importDefault(require("crypto"));
|
|
24
24
|
const prisma_1 = require("../prisma");
|
|
25
25
|
const logger_1 = __importDefault(require("../logger"));
|
|
26
|
+
const identityChanges_1 = require("./identity/identityChanges");
|
|
26
27
|
let ApiKeyService = class ApiKeyService {
|
|
27
28
|
constructor() {
|
|
28
29
|
this.log = logger_1.default.scope('ApiKey');
|
|
@@ -38,8 +39,13 @@ let ApiKeyService = class ApiKeyService {
|
|
|
38
39
|
generateRaw() {
|
|
39
40
|
return crypto_1.default.randomBytes(32).toString('hex');
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The live key for `raw`. A use of it is recorded (`lastUsedAt`);
|
|
44
|
+
* `touch: false` only looks, for a check that isn't a use (a socket's
|
|
45
|
+
* re-check). Likewise verifyPair.
|
|
46
|
+
*/
|
|
47
|
+
verify(raw_1) {
|
|
48
|
+
return __awaiter(this, arguments, void 0, function* (raw, { touch = true } = {}) {
|
|
43
49
|
if (!raw)
|
|
44
50
|
return null;
|
|
45
51
|
const row = yield prisma_1.prisma.apiKey.findUnique({ where: { keyHash: this.hash(raw) } });
|
|
@@ -47,6 +53,8 @@ let ApiKeyService = class ApiKeyService {
|
|
|
47
53
|
return null;
|
|
48
54
|
if (row.expiresAt && row.expiresAt.getTime() <= Date.now())
|
|
49
55
|
return null;
|
|
56
|
+
if (!touch)
|
|
57
|
+
return row;
|
|
50
58
|
prisma_1.prisma.apiKey
|
|
51
59
|
.update({ where: { id: row.id }, data: { lastUsedAt: new Date() } })
|
|
52
60
|
.catch(() => undefined);
|
|
@@ -77,8 +85,8 @@ let ApiKeyService = class ApiKeyService {
|
|
|
77
85
|
return { id: row.id, raw };
|
|
78
86
|
});
|
|
79
87
|
}
|
|
80
|
-
verifyPair(
|
|
81
|
-
return __awaiter(this,
|
|
88
|
+
verifyPair(accessKey_1, token_1) {
|
|
89
|
+
return __awaiter(this, arguments, void 0, function* (accessKey, token, { touch = true } = {}) {
|
|
82
90
|
if (!accessKey || !token)
|
|
83
91
|
return null;
|
|
84
92
|
const user = yield prisma_1.prisma.user.findUnique({ where: { accessKey } });
|
|
@@ -91,6 +99,8 @@ let ApiKeyService = class ApiKeyService {
|
|
|
91
99
|
return null;
|
|
92
100
|
if (row.expiresAt && row.expiresAt.getTime() <= Date.now())
|
|
93
101
|
return null;
|
|
102
|
+
if (!touch)
|
|
103
|
+
return row;
|
|
94
104
|
prisma_1.prisma.apiKey
|
|
95
105
|
.update({ where: { id: row.id }, data: { lastUsedAt: new Date() } })
|
|
96
106
|
.catch(() => undefined);
|
|
@@ -99,7 +109,8 @@ let ApiKeyService = class ApiKeyService {
|
|
|
99
109
|
}
|
|
100
110
|
revoke(id) {
|
|
101
111
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
yield prisma_1.prisma.apiKey.update({ where: { id }, data: { revokedAt: new Date() } });
|
|
112
|
+
const row = yield prisma_1.prisma.apiKey.update({ where: { id }, data: { revokedAt: new Date() } });
|
|
113
|
+
yield (0, identityChanges_1.identityChanged)(row.userId);
|
|
103
114
|
});
|
|
104
115
|
}
|
|
105
116
|
list() {
|
|
@@ -240,7 +240,11 @@ let InterceptorService = class InterceptorService {
|
|
|
240
240
|
throw new Error(`Interceptor not active for session ${sessionId}`);
|
|
241
241
|
return state;
|
|
242
242
|
}
|
|
243
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* `udid` is the session's phone. The dashboard events reach only admins who
|
|
245
|
+
* see it: a captured request's headers and bodies can carry the app's
|
|
246
|
+
* sign-in tokens, and REST's /interceptor routes are admin-only too.
|
|
247
|
+
*/
|
|
244
248
|
emit(evt, udid) {
|
|
245
249
|
for (const l of this.listeners) {
|
|
246
250
|
try {
|
|
@@ -255,7 +259,7 @@ let InterceptorService = class InterceptorService {
|
|
|
255
259
|
broadcast(evt, udid) {
|
|
256
260
|
try {
|
|
257
261
|
const socket = typedi_1.Container.get(SocketServer_1.SocketServer);
|
|
258
|
-
const scope = { udid };
|
|
262
|
+
const scope = { udid, adminOnly: true };
|
|
259
263
|
if (evt.type === 'request') {
|
|
260
264
|
void socket.emitToDashboardForDevices(SocketEvents_1.SocketEvents.INTERCEPTOR_REQUEST, evt.payload, scope);
|
|
261
265
|
}
|
|
@@ -26,6 +26,7 @@ const event_manager_1 = require("../dashboard/event-manager");
|
|
|
26
26
|
const SessionStatus_1 = require("../types/SessionStatus");
|
|
27
27
|
const device_service_1 = require("../data-service/device-service");
|
|
28
28
|
const PhoneNetworkRestore_1 = require("./network/PhoneNetworkRestore");
|
|
29
|
+
const TracingService_1 = require("./TracingService");
|
|
29
30
|
const sessionMemory_1 = require("../sessions/sessionMemory");
|
|
30
31
|
let OrphanSweeper = class OrphanSweeper {
|
|
31
32
|
constructor() {
|
|
@@ -76,6 +77,11 @@ let OrphanSweeper = class OrphanSweeper {
|
|
|
76
77
|
// the ledger's, at boot (PhoneNetworkRestore).
|
|
77
78
|
yield typedi_1.Container.get(PhoneNetworkRestore_1.PhoneNetworkRestore).restoreSession(s.id, 'heartbeat timeout');
|
|
78
79
|
(0, sessionMemory_1.forgetSessionMemory)(s.id);
|
|
80
|
+
// A previous process's session has no span here; this one's ends.
|
|
81
|
+
typedi_1.Container.get(TracingService_1.TracingService).endSessionSpan(s.id, {
|
|
82
|
+
failed: true,
|
|
83
|
+
reason: 'Session heartbeat timeout',
|
|
84
|
+
});
|
|
79
85
|
// This session's claim only, not every row with its udid: the phone
|
|
80
86
|
// may be another session's by now (deviceClaims.ts).
|
|
81
87
|
yield (0, device_service_1.releaseSessionDevices)(s.id);
|
|
@@ -51,6 +51,10 @@ function whereTuple(strategy, selector) {
|
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
/** Just the selector a change is about, for the socket server's scoped emit. */
|
|
55
|
+
function selectorOf(tuple) {
|
|
56
|
+
return { strategy: tuple.strategy, selector: tuple.selector };
|
|
57
|
+
}
|
|
54
58
|
/** The SelectorEvent row for one change. */
|
|
55
59
|
function selectorEventData(tuple, action, userId, reason) {
|
|
56
60
|
return {
|
|
@@ -140,7 +144,7 @@ let SelectorStateService = class SelectorStateService {
|
|
|
140
144
|
return upserted;
|
|
141
145
|
}));
|
|
142
146
|
scopedLog.info(`markFixed: ${ctx.strategy}=${ctx.selector} → pending (api_key=${ctx.apiKeyId})`);
|
|
143
|
-
this.socket.
|
|
147
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_FIXED, serialize(row), selectorOf(ctx));
|
|
144
148
|
return row;
|
|
145
149
|
});
|
|
146
150
|
}
|
|
@@ -174,7 +178,7 @@ let SelectorStateService = class SelectorStateService {
|
|
|
174
178
|
return upserted;
|
|
175
179
|
}));
|
|
176
180
|
scopedLog.info(`mute: ${ctx.strategy}=${ctx.selector} → muted (api_key=${ctx.apiKeyId})`);
|
|
177
|
-
this.socket.
|
|
181
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_MUTED, serialize(row), selectorOf(ctx));
|
|
178
182
|
return row;
|
|
179
183
|
});
|
|
180
184
|
}
|
|
@@ -231,13 +235,13 @@ let SelectorStateService = class SelectorStateService {
|
|
|
231
235
|
scopedLog.info(`unmute: ${ctx.strategy}=${ctx.selector} → ${result ? 'active' : 'deleted'} (api_key=${ctx.apiKeyId})`);
|
|
232
236
|
// Emit the post-state. When the row was deleted, surface the prior tuple
|
|
233
237
|
// so the dashboard can drop it from the muted list.
|
|
234
|
-
this.socket.
|
|
238
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_UNMUTED, result
|
|
235
239
|
? serialize(result)
|
|
236
240
|
: {
|
|
237
241
|
original_strategy: ctx.strategy,
|
|
238
242
|
original_selector: ctx.selector,
|
|
239
243
|
status: 'deleted',
|
|
240
|
-
});
|
|
244
|
+
}, selectorOf(ctx));
|
|
241
245
|
return result;
|
|
242
246
|
});
|
|
243
247
|
}
|
|
@@ -279,13 +283,13 @@ let SelectorStateService = class SelectorStateService {
|
|
|
279
283
|
return row;
|
|
280
284
|
}));
|
|
281
285
|
scopedLog.info(`cancelVerification: ${ctx.strategy}=${ctx.selector} → ${result ? 'active' : 'deleted'} (api_key=${ctx.apiKeyId})`);
|
|
282
|
-
this.socket.
|
|
286
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_CANCELLED, result
|
|
283
287
|
? serialize(result)
|
|
284
288
|
: {
|
|
285
289
|
original_strategy: ctx.strategy,
|
|
286
290
|
original_selector: ctx.selector,
|
|
287
291
|
status: 'deleted',
|
|
288
|
-
});
|
|
292
|
+
}, selectorOf(ctx));
|
|
289
293
|
return result;
|
|
290
294
|
});
|
|
291
295
|
}
|
|
@@ -329,7 +333,7 @@ let SelectorStateService = class SelectorStateService {
|
|
|
329
333
|
return updated;
|
|
330
334
|
}));
|
|
331
335
|
scopedLog.warn(`onHealRecorded: ${ctx.strategy}=${ctx.selector} regressed from ${existing.status} (session=${ctx.sessionId})`);
|
|
332
|
-
this.socket.
|
|
336
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_REGRESSED, serialize(row), selectorOf(ctx));
|
|
333
337
|
return row;
|
|
334
338
|
});
|
|
335
339
|
}
|
|
@@ -39,6 +39,10 @@ const log = logger_1.default.scope('SelectorVerification');
|
|
|
39
39
|
* selector that was never found again was verified as fixed.
|
|
40
40
|
*/
|
|
41
41
|
const CLEAN_BUILDS_TO_RESOLVE = 3;
|
|
42
|
+
/** The selector a row is about, as the socket server's scoped emit names it. */
|
|
43
|
+
function keyOfRow(row) {
|
|
44
|
+
return { strategy: row.original_strategy, selector: row.original_selector };
|
|
45
|
+
}
|
|
42
46
|
/**
|
|
43
47
|
* Periodically scans Pending SelectorState rows and promotes them to Resolved
|
|
44
48
|
* once enough clean CI builds have been observed (no heals on the selector
|
|
@@ -180,7 +184,7 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
180
184
|
return promoted;
|
|
181
185
|
}));
|
|
182
186
|
log.info(`[${row.id}] promoted to resolved (${row.original_strategy}=${row.original_selector})`);
|
|
183
|
-
this.socket.
|
|
187
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_RESOLVED, this.serialize(updated), keyOfRow(row));
|
|
184
188
|
});
|
|
185
189
|
}
|
|
186
190
|
updateProgress(row, cleanBuildCount) {
|
|
@@ -189,7 +193,7 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
189
193
|
where: { id: row.id },
|
|
190
194
|
data: { clean_builds_count: cleanBuildCount, last_event_at: new Date() },
|
|
191
195
|
});
|
|
192
|
-
this.socket.
|
|
196
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_PROGRESS, this.serialize(updated), keyOfRow(row));
|
|
193
197
|
});
|
|
194
198
|
}
|
|
195
199
|
serialize(row) {
|
|
@@ -56,8 +56,6 @@ const typedi_1 = require("typedi");
|
|
|
56
56
|
const uuid_1 = require("uuid");
|
|
57
57
|
const lodash_1 = __importDefault(require("lodash"));
|
|
58
58
|
const axios_1 = __importDefault(require("axios"));
|
|
59
|
-
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
60
|
-
const http_proxy_agent_1 = require("http-proxy-agent");
|
|
61
59
|
const logger_1 = __importDefault(require("../logger"));
|
|
62
60
|
const helpers_1 = require("../helpers");
|
|
63
61
|
const InternalHttpClient_1 = require("../InternalHttpClient");
|
|
@@ -848,7 +846,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
848
846
|
}
|
|
849
847
|
finalizeSession(session_1, device_1, caps_1, driver_1, isRemote_1) {
|
|
850
848
|
return __awaiter(this, arguments, void 0, function* (session, device, caps, driver, isRemote, apiKeyId = null, userId = null, allocatedAt) {
|
|
851
|
-
var _a;
|
|
849
|
+
var _a, _b;
|
|
852
850
|
const sessionId = session.value[0];
|
|
853
851
|
const sessionResponse = session.value[1];
|
|
854
852
|
const requestedCaps = Object.assign({}, ((_a = caps.firstMatch) === null || _a === void 0 ? void 0 : _a[0]) || {}, caps.alwaysMatch || {});
|
|
@@ -865,6 +863,23 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
865
863
|
'xenon.udid': device.udid,
|
|
866
864
|
});
|
|
867
865
|
}
|
|
866
|
+
try {
|
|
867
|
+
yield this.registerSession(sessionId, sessionResponse, device, xenonCapabilities, driver, isRemote, apiKeyId, userId, allocatedAt);
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
// The create fails, so no ending of the session will come to end it.
|
|
871
|
+
tracingService.endSessionSpan(sessionId, {
|
|
872
|
+
failed: true,
|
|
873
|
+
reason: `Xenon could not finish creating the session: ${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`,
|
|
874
|
+
});
|
|
875
|
+
throw err;
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
/** The rest of finalizeSession, once the session's span has started. */
|
|
880
|
+
registerSession(sessionId, sessionResponse, device, xenonCapabilities, driver, isRemote, apiKeyId, userId, allocatedAt) {
|
|
881
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
882
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
868
883
|
// The session takes over the pending claim its phone was allocated with.
|
|
869
884
|
yield (0, device_service_1.claimDeviceForSession)(device, sessionId, {
|
|
870
885
|
session_id: sessionId,
|
|
@@ -1063,19 +1078,8 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1063
1078
|
timeout: exports.REMOTE_CREATE_TIMEOUT_MS,
|
|
1064
1079
|
retry: false,
|
|
1065
1080
|
};
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const proxyUrl = typeof context.pluginArgs.proxy === 'string'
|
|
1069
|
-
? context.pluginArgs.proxy
|
|
1070
|
-
: `http://${context.pluginArgs.proxy.host}:${context.pluginArgs.proxy.port}`;
|
|
1071
|
-
config.httpsAgent = new https_proxy_agent_1.HttpsProxyAgent(proxyUrl, {
|
|
1072
|
-
rejectUnauthorized: context.pluginArgs.tlsRejectUnauthorized,
|
|
1073
|
-
});
|
|
1074
|
-
config.httpAgent = new http_proxy_agent_1.HttpProxyAgent(proxyUrl, {
|
|
1075
|
-
rejectUnauthorized: context.pluginArgs.tlsRejectUnauthorized,
|
|
1076
|
-
});
|
|
1077
|
-
config.proxy = false;
|
|
1078
|
-
}
|
|
1081
|
+
// Its proxy is InternalHttpClient's, by Xenon's one rule: the `proxy`
|
|
1082
|
+
// option, else the environment's (helpers/outboundProxy.ts).
|
|
1079
1083
|
const createdSession = yield this.invokeSessionRequest(config, context.pluginArgs.tlsRejectUnauthorized);
|
|
1080
1084
|
if (createdSession instanceof Error) {
|
|
1081
1085
|
return createdSession;
|
|
@@ -1207,6 +1211,12 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1207
1211
|
if (sessionId) {
|
|
1208
1212
|
typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
|
|
1209
1213
|
(0, sessionMemory_1.forgetSessionMemory)(sessionId);
|
|
1214
|
+
// Whether or not SESSION_MANAGER holds the session (a local one with
|
|
1215
|
+
// the dashboard off), and once: a second delete finds no span.
|
|
1216
|
+
typedi_1.Container.get(TracingService_1.TracingService).endSessionSpan(sessionId, {
|
|
1217
|
+
failed: status === SessionStatus_1.SessionStatus.FAILED,
|
|
1218
|
+
reason,
|
|
1219
|
+
});
|
|
1210
1220
|
// Before the lock's "still in memory?" check, which a dashboard-off
|
|
1211
1221
|
// node fails. On a node this ends the figures it holds for its hub;
|
|
1212
1222
|
// on a hub it collects a node session's last ones. Idempotent.
|
|
@@ -1240,8 +1250,11 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1240
1250
|
logger_1.default.warn(`Port release failed: ${err.message}`);
|
|
1241
1251
|
}
|
|
1242
1252
|
}
|
|
1243
|
-
// 1. iOS Profiling Archival
|
|
1244
|
-
|
|
1253
|
+
// 1. iOS Profiling Archival. Only on an iPhone, the one place
|
|
1254
|
+
// EventManager.onSessionStarted starts it. A simulator's driver refuses
|
|
1255
|
+
// the stop without relaxed security, and the refused stop counted as a
|
|
1256
|
+
// failed command of the session: every simulator session ended failed.
|
|
1257
|
+
if (device && ((_a = device.platform) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'ios' && device.realDevice === true) {
|
|
1245
1258
|
this.logger.info(`[${sessionId}] Stopping iOS profiling for asset archival`);
|
|
1246
1259
|
try {
|
|
1247
1260
|
const traceBase64 = yield session.stopPerformanceRecording();
|
|
@@ -1323,6 +1336,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1323
1336
|
SessionManager_1.SESSION_MANAGER.removeSession(sessionId);
|
|
1324
1337
|
typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
|
|
1325
1338
|
(0, sessionMemory_1.forgetSessionMemory)(sessionId);
|
|
1339
|
+
typedi_1.Container.get(TracingService_1.TracingService).endSessionSpan(sessionId, { failed: true, reason });
|
|
1326
1340
|
yield typedi_1.Container.get(SessionMetricsService_1.SessionMetricsService).stop(sessionId);
|
|
1327
1341
|
}));
|
|
1328
1342
|
});
|
|
@@ -15,6 +15,7 @@ const typedi_1 = require("typedi");
|
|
|
15
15
|
const logger_1 = __importDefault(require("../logger"));
|
|
16
16
|
const config_1 = require("../config");
|
|
17
17
|
const SocketEvents_1 = require("../enums/SocketEvents");
|
|
18
|
+
const outboundProxy_1 = require("../helpers/outboundProxy");
|
|
18
19
|
let SocketClient = class SocketClient {
|
|
19
20
|
constructor() {
|
|
20
21
|
this.socket = null;
|
|
@@ -46,6 +47,16 @@ let SocketClient = class SocketClient {
|
|
|
46
47
|
reconnectionDelay: 1000,
|
|
47
48
|
reconnectionDelayMax: 5000,
|
|
48
49
|
auth: socketAuth,
|
|
50
|
+
// The proxy the node's other calls to its hub take (its phone reports,
|
|
51
|
+
// its JWKS fetch); it used to go straight to the hub whatever the
|
|
52
|
+
// environment said. Polling goes as plain requests, as axios sends
|
|
53
|
+
// them; only the WebSocket upgrade needs a CONNECT tunnel, and when the
|
|
54
|
+
// proxy refuses one (a stock Squid allows port 443 only) the
|
|
55
|
+
// connection stays on polling.
|
|
56
|
+
transportOptions: {
|
|
57
|
+
polling: { agent: (0, outboundProxy_1.proxyAgentFor)(normalizedHubUrl) },
|
|
58
|
+
websocket: { agent: (0, outboundProxy_1.socketProxyAgentFor)(normalizedHubUrl) },
|
|
59
|
+
},
|
|
49
60
|
});
|
|
50
61
|
this.socket.on('connect', () => {
|
|
51
62
|
var _a, _b, _c;
|