@xenon-device-management/xenon 2.16.0 → 2.17.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 +2 -2
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-2W4JvEyZ.js → AnnotationOverlay-CjmMCS0S.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-GmckX_7r.js → ApiKeyGate-B4Ob0v3C.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-DqODzkyo.js → DeviceMosaicView-DTVOB_NO.js} +1 -1
- package/lib/public/assets/{EmptyState-7hETKEWb.js → EmptyState-DcEG-oAi.js} +1 -1
- package/lib/public/assets/{FieldGroup-GOgssFEP.js → FieldGroup-CtnCDshE.js} +1 -1
- package/lib/public/assets/{FilterMenu-Yvf6RGMd.js → FilterMenu-BK0dE9cc.js} +1 -1
- package/lib/public/assets/{Menu-KrPnfdsf.js → Menu-eKsmLpKS.js} +1 -1
- package/lib/public/assets/{Modal-C6WjZFeI.js → Modal-DPwop3Lz.js} +1 -1
- package/lib/public/assets/{RecordingPage-CSRIhZEh.js → RecordingPage-CfgSkZkB.js} +1 -1
- package/lib/public/assets/{RecordingsPage-CB8ST3nE.js → RecordingsPage-Bzl9pTUY.js} +1 -1
- package/lib/public/assets/{SegmentedControl-C9o90KId.js → SegmentedControl-B_TZTrPm.js} +1 -1
- package/lib/public/assets/{SettingCard-CPm3w9l2.js → SettingCard-DlmvZJPM.js} +1 -1
- package/lib/public/assets/{Table-DwIgERRt.js → Table-BigPWKXi.js} +1 -1
- package/lib/public/assets/{activity-DrLbHifn.js → activity-CvuZdQYu.js} +1 -1
- package/lib/public/assets/{ai-settings-DU_Ls06P.js → ai-settings-lI2UJ2n7.js} +1 -1
- package/lib/public/assets/{api-keys-CWYdEzJ_.js → api-keys-CPgf9XJM.js} +1 -1
- package/lib/public/assets/{apps-BqNVdln7.js → apps-DIzOvtUp.js} +1 -1
- package/lib/public/assets/{arrow-left-BtB0_Jg_.js → arrow-left-DiP5ywBT.js} +1 -1
- package/lib/public/assets/{arrow-right-C8NhdtyN.js → arrow-right-DtW_8hm5.js} +1 -1
- package/lib/public/assets/{arrow-up-right-gMg0ffvv.js → arrow-up-right-C89FbNU0.js} +1 -1
- package/lib/public/assets/{auth-shell-CiYZ3N4M.js → auth-shell-B7sUDb--.js} +2 -2
- package/lib/public/assets/{builds-page-EaaqDmLN.js → builds-page-BBhvrMfA.js} +1 -1
- package/lib/public/assets/{button-DDau4Axv.js → button-DugsdUw1.js} +1 -1
- package/lib/public/assets/{calendar-DQ83B1ed.js → calendar-grKd08ZH.js} +1 -1
- package/lib/public/assets/{check-K1ovTQam.js → check-ORjyNfnX.js} +1 -1
- package/lib/public/assets/{chevron-right-Biq00pMt.js → chevron-right-Ei2FxUXq.js} +1 -1
- package/lib/public/assets/{circle-check-CBv88cH7.js → circle-check-Day0HjW4.js} +1 -1
- package/lib/public/assets/{circle-x-Bmltailn.js → circle-x-CHxSRLHp.js} +1 -1
- package/lib/public/assets/{clock-BPrld-3h.js → clock-BDHewWTx.js} +1 -1
- package/lib/public/assets/{copy-t0QB14JI.js → copy-CTwT4I3D.js} +1 -1
- package/lib/public/assets/{device-explorer-BgGZ8AvV.js → device-explorer-DlhrKJ-g.js} +1 -1
- package/lib/public/assets/{download-BroHxNOh.js → download-D3bjz_vr.js} +1 -1
- package/lib/public/assets/{forgot-password-Bjmf49i6.js → forgot-password-BNBLi023.js} +1 -1
- package/lib/public/assets/{h264Stream-DeD__6wa.js → h264Stream-C51Au2i_.js} +1 -1
- package/lib/public/assets/{index-B0gQkEg0.js → index-Dk5J6HB1.js} +2 -2
- package/lib/public/assets/{index-B0JeuZbg.js → index-DncSz5LG.js} +1 -1
- package/lib/public/assets/{index-72oF89LJ.js → index-Z7_BY3pJ.js} +1 -1
- package/lib/public/assets/{input-DIc4MSC7.js → input-bTXM4l09.js} +1 -1
- package/lib/public/assets/{line-chart-BcWm2vW8.js → line-chart-B3YmVYyr.js} +1 -1
- package/lib/public/assets/{list-checks-B06IJJad.js → list-checks-BClKEj1-.js} +1 -1
- package/lib/public/assets/{lock-pVLvnN30.js → lock-CZsenX3D.js} +1 -1
- package/lib/public/assets/{login-CN77vy--.js → login-B6A6tOp9.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CSdzQSkY.js → maintenance-settings-CmCJGy7O.js} +1 -1
- package/lib/public/assets/{monitor-BtrN5IkI.js → monitor-BdTNEglU.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CNsQfo6w.js → mouse-pointer-2-BM8CaLtt.js} +1 -1
- package/lib/public/assets/{network-BSfA8ZxG.js → network-BS6vUc1x.js} +1 -1
- package/lib/public/assets/{overview-BfTebCK5.js → overview-Yfk_Y7g3.js} +1 -1
- package/lib/public/assets/{page-header-D8KUX6Qg.js → page-header-DhXCwpml.js} +1 -1
- package/lib/public/assets/{play-Cvc8xzic.js → play-D08gP1td.js} +1 -1
- package/lib/public/assets/{plus-C9Zj84UF.js → plus-BKboorfa.js} +1 -1
- package/lib/public/assets/{profile-page-Bi27STgs.js → profile-page-Cd9NodrA.js} +1 -1
- package/lib/public/assets/{reset-password-CPsOuwSu.js → reset-password-acLDyyDW.js} +1 -1
- package/lib/public/assets/{runbook-page-DEQ-DBOX.js → runbook-page-CWLkC1Fd.js} +1 -1
- package/lib/public/assets/{select-BWpnhWXj.js → select-DZoRKNti.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BefSCSpe.js → selector-detail-redirect-DS4qrt84.js} +1 -1
- package/lib/public/assets/{selector-health-page-MSk96Zz7.js → selector-health-page-TpmN61G3.js} +1 -1
- package/lib/public/assets/{session-detail-page-DPyBNef_.js → session-detail-page-C0wYaXqy.js} +1 -1
- package/lib/public/assets/{settings-V6x-cXwy.js → settings-CxPGEY2j.js} +1 -1
- package/lib/public/assets/{stat-tile-Duex-GZD.js → stat-tile-DUpyYgl-.js} +1 -1
- package/lib/public/assets/{tablet-BBcoqBmL.js → tablet-SA02BJNQ.js} +1 -1
- package/lib/public/assets/{teams-ts8p5rEh.js → teams-jamD0Kxr.js} +1 -1
- package/lib/public/assets/{trash-2-CZ2ew6AZ.js → trash-2-DO8fv6w4.js} +1 -1
- package/lib/public/assets/{upload-Dv99y_le.js → upload-CaiGCStb.js} +1 -1
- package/lib/public/assets/{use-builds-data-DrYC8JCF.js → use-builds-data-jS-fcvVf.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C7QkBNkP.js → use-password-reset-mode-BzoenegM.js} +1 -1
- package/lib/public/assets/{users-gJ5Y01yo.js → users-Bnw7Vs_B.js} +1 -1
- package/lib/public/assets/{users-D7tKMESH.js → users-DHvDrriG.js} +1 -1
- package/lib/public/assets/{video-off-CqZVuZGs.js → video-off-FEvy0w5U.js} +1 -1
- package/lib/public/assets/{webhook-settings-DI5DSW99.js → webhook-settings-CBVB5lxi.js} +1 -1
- package/lib/public/assets/{zap-ClcHFxfo.js → zap-BMrTJO6M.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +1 -1
- package/lib/src/app/index.js +2 -0
- package/lib/src/app/openapi/grid.yaml +102 -0
- package/lib/src/dashboard/event-manager.js +5 -8
- package/lib/src/device-utils.js +11 -2
- package/lib/src/gateway/nodeAsk.js +57 -0
- package/lib/src/gateway/nodeSessionDeviceLogs.js +35 -0
- package/lib/src/plugin.js +4 -1
- package/lib/src/scripts/initialize-database.js +0 -6
- package/lib/src/scripts/run-migrations.js +123 -880
- package/lib/src/services/SessionLifecycleService.js +20 -5
- package/lib/src/services/interceptor/CertManager.js +50 -16
- package/lib/src/services/logcat/NodeDeviceLogStore.js +99 -0
- package/lib/src/services/logcat/NodeDeviceLogsCollector.js +164 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +199 -44
- package/lib/src/services/logcat/nodeDeviceLogs.js +91 -0
- package/lib/src/services/metrics/nodeMetrics.js +20 -49
- package/lib/src/sessions/RemoteSession.js +11 -2
- package/lib/src/sessions/SessionManager.js +28 -1
- package/lib/test/helpers/fake-logcat.js +80 -0
- package/lib/test/helpers/hub-node-pair.js +227 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +15 -0
- package/lib/test/unit/hub-node-iphone-readiness.spec.js +134 -0
- package/lib/test/unit/hub-node-session-device-logs.spec.js +194 -0
- package/lib/test/unit/hub-node-session-metrics.spec.js +24 -192
- package/lib/test/unit/hub-remote-session-heartbeat.spec.js +8 -1
- package/lib/test/unit/interceptor/CertManager.spec.js +89 -0
- package/lib/test/unit/node-device-log-store.spec.js +102 -0
- package/lib/test/unit/node-device-logs-collector.spec.js +343 -0
- package/lib/test/unit/node-gateway-create-appium-server.spec.js +8 -1
- package/lib/test/unit/node-hub-session-owner.spec.js +8 -1
- package/lib/test/unit/node-session-device-logs-route.spec.js +103 -0
- package/lib/test/unit/run-migrations-database.spec.js +57 -424
- package/lib/test/unit/run-migrations.test.js +74 -674
- package/lib/test/unit/session-device-logs-hub-node.spec.js +340 -0
- package/lib/test/unit/session-device-logs-lifecycle.spec.js +137 -0
- package/lib/test/unit/session-device-logs.spec.js +32 -94
- package/lib/test/unit/session-log-indexes.spec.js +15 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/schema.json +1 -1
|
@@ -1736,6 +1736,82 @@ paths:
|
|
|
1736
1736
|
$ref: '#/components/responses/GridNodeUnknownSession'
|
|
1737
1737
|
'503':
|
|
1738
1738
|
$ref: '#/components/responses/GridNodeTokenUnavailable'
|
|
1739
|
+
/api/node/sessions/{sessionId}/device-logs:
|
|
1740
|
+
get:
|
|
1741
|
+
operationId: getGridNodeSessionDeviceLogs
|
|
1742
|
+
summary: Collect a session's device log lines from this node
|
|
1743
|
+
description: |
|
|
1744
|
+
Node only: a hub collects the device log lines a node has recorded
|
|
1745
|
+
for a session on one of its phones, as soon as the session is
|
|
1746
|
+
created, every 10 s after that, and once more when the session
|
|
1747
|
+
ends. The hub writes them as the session's Device logs. Only a
|
|
1748
|
+
server started with `hub` set has this route.
|
|
1749
|
+
|
|
1750
|
+
The node records each session its hub created, with the same limit
|
|
1751
|
+
as a session on the hub's own phone (10,000 lines, then up to 2,000
|
|
1752
|
+
more errors), and keeps the lines in memory until the hub collects
|
|
1753
|
+
them, an ended session's for 10 minutes. If no hub asks within 2
|
|
1754
|
+
minutes of the start, the node stops recording that session.
|
|
1755
|
+
|
|
1756
|
+
Each line has a number, `seq`, counting up from 1. `after` asks for
|
|
1757
|
+
the lines after that number, and the node drops the others, so a
|
|
1758
|
+
later call never returns them again. An answer holds at most 2,000
|
|
1759
|
+
lines; `more` says the node has others, to ask for at once. `state`
|
|
1760
|
+
is `recording`, `ended`, or `off` (the node doesn't record this
|
|
1761
|
+
session). `timestamp` is the line's moment by the node's clock.
|
|
1762
|
+
|
|
1763
|
+
It is answered before the login, with the same rule as
|
|
1764
|
+
`GET /api/node/sessions/{sessionId}`: no credential with
|
|
1765
|
+
per-command auth off or auth disabled, otherwise the hub's session
|
|
1766
|
+
token for that session. Every answer from a node carries
|
|
1767
|
+
`x-xenon-node-device-logs: 1`. It isn't rate-limited.
|
|
1768
|
+
tags:
|
|
1769
|
+
- Hub-Node
|
|
1770
|
+
security:
|
|
1771
|
+
- HubToken: []
|
|
1772
|
+
- {}
|
|
1773
|
+
parameters:
|
|
1774
|
+
- $ref: '#/components/parameters/GridNodeSessionId'
|
|
1775
|
+
- in: query
|
|
1776
|
+
name: after
|
|
1777
|
+
required: false
|
|
1778
|
+
description: A line number; only later lines are returned, and the others are dropped. Anything that isn't a plain number is ignored.
|
|
1779
|
+
schema:
|
|
1780
|
+
type: integer
|
|
1781
|
+
example: 2000
|
|
1782
|
+
responses:
|
|
1783
|
+
'200':
|
|
1784
|
+
description: The session's lines.
|
|
1785
|
+
headers:
|
|
1786
|
+
x-xenon-node-device-logs:
|
|
1787
|
+
description: Always `1` on a node.
|
|
1788
|
+
schema:
|
|
1789
|
+
type: string
|
|
1790
|
+
example: '1'
|
|
1791
|
+
content:
|
|
1792
|
+
application/json:
|
|
1793
|
+
schema:
|
|
1794
|
+
type: object
|
|
1795
|
+
properties:
|
|
1796
|
+
value:
|
|
1797
|
+
$ref: '#/components/schemas/GridNodeDeviceLogs'
|
|
1798
|
+
example:
|
|
1799
|
+
value:
|
|
1800
|
+
state: recording
|
|
1801
|
+
more: false
|
|
1802
|
+
lines:
|
|
1803
|
+
- seq: 2001
|
|
1804
|
+
message: '10-05 14:20:02.114 4127 4127 I ActivityManager: Start proc 4127:com.example.shop/u0a215'
|
|
1805
|
+
timestamp: 1791111602114
|
|
1806
|
+
- seq: 2002
|
|
1807
|
+
message: '10-05 14:20:02.391 4127 4127 E AndroidRuntime: FATAL EXCEPTION: main'
|
|
1808
|
+
timestamp: 1791111602391
|
|
1809
|
+
'401':
|
|
1810
|
+
$ref: '#/components/responses/GridNotANodeUnauthorized'
|
|
1811
|
+
'404':
|
|
1812
|
+
$ref: '#/components/responses/GridNodeUnknownSession'
|
|
1813
|
+
'503':
|
|
1814
|
+
$ref: '#/components/responses/GridNodeTokenUnavailable'
|
|
1739
1815
|
/api/status:
|
|
1740
1816
|
get:
|
|
1741
1817
|
operationId: getGridServerStatus
|
|
@@ -2304,6 +2380,32 @@ components:
|
|
|
2304
2380
|
type: string
|
|
2305
2381
|
nullable: true
|
|
2306
2382
|
description: The app the app figures are for (Android), else null.
|
|
2383
|
+
GridNodeDeviceLogs:
|
|
2384
|
+
type: object
|
|
2385
|
+
properties:
|
|
2386
|
+
state:
|
|
2387
|
+
type: string
|
|
2388
|
+
enum:
|
|
2389
|
+
- recording
|
|
2390
|
+
- ended
|
|
2391
|
+
- 'off'
|
|
2392
|
+
more:
|
|
2393
|
+
type: boolean
|
|
2394
|
+
description: The node holds more lines than this answer; ask again with the last `seq`.
|
|
2395
|
+
lines:
|
|
2396
|
+
type: array
|
|
2397
|
+
items:
|
|
2398
|
+
type: object
|
|
2399
|
+
properties:
|
|
2400
|
+
seq:
|
|
2401
|
+
type: integer
|
|
2402
|
+
description: The line's number in the session, from 1.
|
|
2403
|
+
message:
|
|
2404
|
+
type: string
|
|
2405
|
+
description: The line as the device printed it.
|
|
2406
|
+
timestamp:
|
|
2407
|
+
type: integer
|
|
2408
|
+
description: Epoch milliseconds, by the node's clock.
|
|
2307
2409
|
GridWebDriverError:
|
|
2308
2410
|
type: object
|
|
2309
2411
|
description: A WebDriver-style error, as Appium answers one.
|
|
@@ -79,6 +79,7 @@ const NotificationService_1 = require("../services/NotificationService");
|
|
|
79
79
|
const SessionMetricsService_1 = require("../services/metrics/SessionMetricsService");
|
|
80
80
|
const SessionDeviceLogs_1 = require("../services/logcat/SessionDeviceLogs");
|
|
81
81
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
82
|
+
const nodeDeviceLogs_1 = require("../services/logcat/nodeDeviceLogs");
|
|
82
83
|
const sessionCommandSummary_1 = require("./sessionCommandSummary");
|
|
83
84
|
const commandLogFields_1 = require("./commandLogFields");
|
|
84
85
|
const typedi_2 = require("typedi");
|
|
@@ -156,19 +157,15 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
156
157
|
// once its row exists (the lines point at it), unless the session turned
|
|
157
158
|
// it off: an Android phone's stream, or the log an iPhone's or
|
|
158
159
|
// simulator's driver captures. Not waited for: an Android phone's clock
|
|
159
|
-
// and log stream are read in the background.
|
|
160
|
+
// and log stream are read in the background. A node's phone's lines are
|
|
161
|
+
// collected from the node, through the session.
|
|
160
162
|
const deviceLogs = typedi_1.Container.get(SessionDeviceLogs_1.SessionDeviceLogs);
|
|
161
163
|
if (capabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.SAVE_DEVICE_LOGS] === false) {
|
|
162
164
|
void deviceLogs.noteOff(session.getId());
|
|
163
165
|
}
|
|
164
166
|
else {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
device,
|
|
168
|
-
since: session.allocatedAt,
|
|
169
|
-
driverLog: (_d = (_c = session).deviceLog) === null || _d === void 0 ? void 0 : _d.call(_c),
|
|
170
|
-
appUnderTest: (_f = (_e = session).appUnderTest) === null || _f === void 0 ? void 0 : _f.call(_e),
|
|
171
|
-
});
|
|
167
|
+
const deviceLogSource = (0, nodeDeviceLogs_1.nodeDeviceLogsSourceOf)(session);
|
|
168
|
+
void deviceLogs.start(Object.assign({ sessionId: session.getId(), device, since: session.allocatedAt, driverLog: (_d = (_c = session).deviceLog) === null || _d === void 0 ? void 0 : _d.call(_c), appUnderTest: (_f = (_e = session).appUnderTest) === null || _f === void 0 ? void 0 : _f.call(_e) }, (deviceLogSource ? { source: deviceLogSource } : {})));
|
|
172
169
|
}
|
|
173
170
|
// Emit session started event
|
|
174
171
|
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices(SocketEvents_1.SocketEvents.SESSION_STARTED, Object.assign(Object.assign({}, createData), { status: 'running', build_name: buildName }), { udid: device.udid });
|
package/lib/src/device-utils.js
CHANGED
|
@@ -275,8 +275,17 @@ function allocateDeviceForSession(capability_1, deviceTimeOutMs_1, deviceQueryIn
|
|
|
275
275
|
// We have a locked device here
|
|
276
276
|
if (device !== null) {
|
|
277
277
|
const lockedDevice = device;
|
|
278
|
-
//
|
|
279
|
-
|
|
278
|
+
// Ready for a session: asked only of a phone this server drives. The
|
|
279
|
+
// check is this machine's (an iPhone's WebDriverAgent on 127.0.0.1, and a
|
|
280
|
+
// go-ios stream started to recover it). Another server's phone, a node's
|
|
281
|
+
// on a hub, is checked by that server when it allocates the phone the hub
|
|
282
|
+
// pins. Through 2.15 the hub checked it too: with the node on another
|
|
283
|
+
// machine nothing answered and the session was refused as unhealthy, and
|
|
284
|
+
// with both on one Mac the hub started a WebDriverAgent of its own on the
|
|
285
|
+
// node's iPhone.
|
|
286
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
287
|
+
const ownPhone = (0, localDeviceHosts_1.isOwnDevice)((0, localDeviceHosts_1.localDeviceHosts)(context.pluginArgs, context.port), context.nodeId, lockedDevice);
|
|
288
|
+
if (!lockedDevice.cloud && ownPhone) {
|
|
280
289
|
const platform = lockedDevice.platform.toLowerCase();
|
|
281
290
|
const managers = yield getDeviceManager().deviceInstances();
|
|
282
291
|
const manager = managers.find((m) => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OlderNodes = exports.OLDER_NODE_RECHECK_MS = void 0;
|
|
4
|
+
exports.readNodeReply = readNodeReply;
|
|
5
|
+
/**
|
|
6
|
+
* The node's answer at a route whose answers carry `header`. `answerOf` reads
|
|
7
|
+
* a 200's `value`, or returns null when it isn't one.
|
|
8
|
+
*/
|
|
9
|
+
function readNodeReply(header, status, headers, data, answerOf) {
|
|
10
|
+
if (!(headers === null || headers === void 0 ? void 0 : headers[header])) {
|
|
11
|
+
// An older node answers its login's 401, an unknown route's 404, or a
|
|
12
|
+
// catch-all's 2xx. Anything else without the header (a proxy's 502 in
|
|
13
|
+
// front of a node) is an outage: ask again, never give the node up.
|
|
14
|
+
if (status === 401 || status === 404 || (status >= 200 && status < 300)) {
|
|
15
|
+
return { kind: 'unsupported', status };
|
|
16
|
+
}
|
|
17
|
+
return { kind: 'unavailable', reason: `answered ${status}` };
|
|
18
|
+
}
|
|
19
|
+
if (status === 404)
|
|
20
|
+
return { kind: 'refused' };
|
|
21
|
+
const answer = status === 200 ? answerOf(data === null || data === void 0 ? void 0 : data.value) : null;
|
|
22
|
+
if (answer !== null)
|
|
23
|
+
return { kind: 'answer', answer };
|
|
24
|
+
return { kind: 'unavailable', reason: `answered ${status}` };
|
|
25
|
+
}
|
|
26
|
+
/** How long a hub leaves a node without a route alone before asking it again. */
|
|
27
|
+
exports.OLDER_NODE_RECHECK_MS = 10 * 60000;
|
|
28
|
+
/**
|
|
29
|
+
* Hub side: which nodes lack a route (an older Xenon). Their sessions go
|
|
30
|
+
* without what the route serves; the hub says so once per node, and asks
|
|
31
|
+
* again after OLDER_NODE_RECHECK_MS, so a node upgraded in place is picked
|
|
32
|
+
* up without restarting the hub.
|
|
33
|
+
*/
|
|
34
|
+
class OlderNodes {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.now = () => Date.now();
|
|
37
|
+
this.unsupportedUntil = new Map();
|
|
38
|
+
this.warned = new Set();
|
|
39
|
+
}
|
|
40
|
+
shouldAsk(origin) {
|
|
41
|
+
const until = this.unsupportedUntil.get(origin);
|
|
42
|
+
if (until === undefined)
|
|
43
|
+
return true;
|
|
44
|
+
if (until > this.now())
|
|
45
|
+
return false;
|
|
46
|
+
this.unsupportedUntil.delete(origin);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
unsupported(origin, status) {
|
|
50
|
+
this.unsupportedUntil.set(origin, this.now() + exports.OLDER_NODE_RECHECK_MS);
|
|
51
|
+
if (this.warned.has(origin))
|
|
52
|
+
return;
|
|
53
|
+
this.warned.add(origin);
|
|
54
|
+
this.logger.warn(this.warning(origin, status));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.OlderNodes = OlderNodes;
|
|
@@ -0,0 +1,35 @@
|
|
|
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.nodeSessionDeviceLogsHandler = nodeSessionDeviceLogsHandler;
|
|
7
|
+
exports.registerNodeSessionDeviceLogs = registerNodeSessionDeviceLogs;
|
|
8
|
+
const typedi_1 = require("typedi");
|
|
9
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const commandAuth_1 = require("../middleware/commandAuth");
|
|
12
|
+
const nodeDeviceLogs_1 = require("../services/logcat/nodeDeviceLogs");
|
|
13
|
+
const NodeDeviceLogStore_1 = require("../services/logcat/NodeDeviceLogStore");
|
|
14
|
+
const hubSessionToken_1 = require("./hubSessionToken");
|
|
15
|
+
const nodeSessionMetrics_1 = require("./nodeSessionMetrics");
|
|
16
|
+
const nodeSessionStatus_1 = require("./nodeSessionStatus");
|
|
17
|
+
function nodeSessionDeviceLogsHandler(deps) {
|
|
18
|
+
return (req, res) => {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
res.setHeader(nodeDeviceLogs_1.NODE_DEVICE_LOGS_HEADER, '1');
|
|
21
|
+
const sessionId = String((_b = (_a = req.params) === null || _a === void 0 ? void 0 : _a.sessionId) !== null && _b !== void 0 ? _b : '');
|
|
22
|
+
(0, nodeSessionStatus_1.answerForHub)(deps, req, res, sessionId, 'Session device logs', () => { var _a; return res.status(200).json({ value: deps.read(sessionId, (0, nodeSessionMetrics_1.parseAfter)((_a = req.query) === null || _a === void 0 ? void 0 : _a.after)) }); });
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** A node's route, ahead of its login, like the session-status route. A hub or standalone server has none. */
|
|
26
|
+
function registerNodeSessionDeviceLogs(router, pluginArgs) {
|
|
27
|
+
if (pluginArgs.hub === undefined)
|
|
28
|
+
return;
|
|
29
|
+
router.get(nodeDeviceLogs_1.NODE_DEVICE_LOGS_ROUTE, nodeSessionDeviceLogsHandler({
|
|
30
|
+
hubTokens: new hubSessionToken_1.HubSessionTokenVerifier(pluginArgs.hub),
|
|
31
|
+
enforced: () => (0, commandAuth_1.commandAuthEnabled)() && config_1.config.authDisabled !== true,
|
|
32
|
+
read: (sessionId, after) => typedi_1.Container.get(NodeDeviceLogStore_1.NodeDeviceLogStore).read(sessionId, after),
|
|
33
|
+
logger: logger_1.default.scope('NodeSessionDeviceLogs'),
|
|
34
|
+
}));
|
|
35
|
+
}
|
package/lib/src/plugin.js
CHANGED
|
@@ -68,6 +68,7 @@ const appiumUmbrella_1 = require("./sessions/appiumUmbrella");
|
|
|
68
68
|
const LiveSessionOwners_1 = require("./services/device-access/LiveSessionOwners");
|
|
69
69
|
const sessionMemory_1 = require("./sessions/sessionMemory");
|
|
70
70
|
const SessionMetricsService_1 = require("./services/metrics/SessionMetricsService");
|
|
71
|
+
const SessionDeviceLogs_1 = require("./services/logcat/SessionDeviceLogs");
|
|
71
72
|
const PhoneNetworkRestore_1 = require("./services/network/PhoneNetworkRestore");
|
|
72
73
|
const config_1 = require("./config");
|
|
73
74
|
const SessionManager_1 = require("./sessions/SessionManager");
|
|
@@ -120,8 +121,10 @@ class XenonPlugin extends base_plugin_1.default {
|
|
|
120
121
|
reason: (0, shutdownReason_1.unexpectedShutdownReason)(cause),
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
|
-
if (sessionId)
|
|
124
|
+
if (sessionId) {
|
|
124
125
|
yield typedi_1.Container.get(SessionMetricsService_1.SessionMetricsService).stop(sessionId);
|
|
126
|
+
yield typedi_1.Container.get(SessionDeviceLogs_1.SessionDeviceLogs).stop(sessionId);
|
|
127
|
+
}
|
|
125
128
|
// Appium's new-command timeout ends a session here, not in deleteSession:
|
|
126
129
|
// the phone's network (profile, interceptor proxy) and the capture are
|
|
127
130
|
// put back before the phone is released.
|
|
@@ -30,18 +30,12 @@ const run_migrations_1 = require("./run-migrations");
|
|
|
30
30
|
* prisma/schema.prisma's provider to `databaseProvider`. With `postgresql`,
|
|
31
31
|
* that left a PostgreSQL schema in the checkout that the SQLite file couldn't
|
|
32
32
|
* use.
|
|
33
|
-
*
|
|
34
|
-
* It first warns of changes to prisma/schema.prisma that no migration makes.
|
|
35
|
-
* `npm run db:generate` gives the developer's database a migration history, and
|
|
36
|
-
* from then on it is updated with `migrate deploy`, which never applies them,
|
|
37
|
-
* while the log said the schema was in sync.
|
|
38
33
|
*/
|
|
39
34
|
function main() {
|
|
40
35
|
return __awaiter(this, void 0, void 0, function* () {
|
|
41
36
|
// A PostgreSQL URL would otherwise reach the history read and fail there,
|
|
42
37
|
// under a message about the file.
|
|
43
38
|
(0, database_check_1.assertSupportedDatabase)();
|
|
44
|
-
(0, run_migrations_1.warnOfUnmigratedSchemaChanges)();
|
|
45
39
|
yield (0, run_migrations_1.syncDatabaseSchema)();
|
|
46
40
|
logger_1.default.info('[DBInit] Generating Prisma Client...');
|
|
47
41
|
(0, node_child_process_1.execSync)('npx prisma generate', {
|