@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.
Files changed (114) hide show
  1. package/README.md +2 -2
  2. package/lib/package.json +1 -1
  3. package/lib/public/assets/{AnnotationOverlay-2W4JvEyZ.js → AnnotationOverlay-CjmMCS0S.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-GmckX_7r.js → ApiKeyGate-B4Ob0v3C.js} +1 -1
  5. package/lib/public/assets/{DeviceMosaicView-DqODzkyo.js → DeviceMosaicView-DTVOB_NO.js} +1 -1
  6. package/lib/public/assets/{EmptyState-7hETKEWb.js → EmptyState-DcEG-oAi.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-GOgssFEP.js → FieldGroup-CtnCDshE.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-Yvf6RGMd.js → FilterMenu-BK0dE9cc.js} +1 -1
  9. package/lib/public/assets/{Menu-KrPnfdsf.js → Menu-eKsmLpKS.js} +1 -1
  10. package/lib/public/assets/{Modal-C6WjZFeI.js → Modal-DPwop3Lz.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CSRIhZEh.js → RecordingPage-CfgSkZkB.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-CB8ST3nE.js → RecordingsPage-Bzl9pTUY.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-C9o90KId.js → SegmentedControl-B_TZTrPm.js} +1 -1
  14. package/lib/public/assets/{SettingCard-CPm3w9l2.js → SettingCard-DlmvZJPM.js} +1 -1
  15. package/lib/public/assets/{Table-DwIgERRt.js → Table-BigPWKXi.js} +1 -1
  16. package/lib/public/assets/{activity-DrLbHifn.js → activity-CvuZdQYu.js} +1 -1
  17. package/lib/public/assets/{ai-settings-DU_Ls06P.js → ai-settings-lI2UJ2n7.js} +1 -1
  18. package/lib/public/assets/{api-keys-CWYdEzJ_.js → api-keys-CPgf9XJM.js} +1 -1
  19. package/lib/public/assets/{apps-BqNVdln7.js → apps-DIzOvtUp.js} +1 -1
  20. package/lib/public/assets/{arrow-left-BtB0_Jg_.js → arrow-left-DiP5ywBT.js} +1 -1
  21. package/lib/public/assets/{arrow-right-C8NhdtyN.js → arrow-right-DtW_8hm5.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-gMg0ffvv.js → arrow-up-right-C89FbNU0.js} +1 -1
  23. package/lib/public/assets/{auth-shell-CiYZ3N4M.js → auth-shell-B7sUDb--.js} +2 -2
  24. package/lib/public/assets/{builds-page-EaaqDmLN.js → builds-page-BBhvrMfA.js} +1 -1
  25. package/lib/public/assets/{button-DDau4Axv.js → button-DugsdUw1.js} +1 -1
  26. package/lib/public/assets/{calendar-DQ83B1ed.js → calendar-grKd08ZH.js} +1 -1
  27. package/lib/public/assets/{check-K1ovTQam.js → check-ORjyNfnX.js} +1 -1
  28. package/lib/public/assets/{chevron-right-Biq00pMt.js → chevron-right-Ei2FxUXq.js} +1 -1
  29. package/lib/public/assets/{circle-check-CBv88cH7.js → circle-check-Day0HjW4.js} +1 -1
  30. package/lib/public/assets/{circle-x-Bmltailn.js → circle-x-CHxSRLHp.js} +1 -1
  31. package/lib/public/assets/{clock-BPrld-3h.js → clock-BDHewWTx.js} +1 -1
  32. package/lib/public/assets/{copy-t0QB14JI.js → copy-CTwT4I3D.js} +1 -1
  33. package/lib/public/assets/{device-explorer-BgGZ8AvV.js → device-explorer-DlhrKJ-g.js} +1 -1
  34. package/lib/public/assets/{download-BroHxNOh.js → download-D3bjz_vr.js} +1 -1
  35. package/lib/public/assets/{forgot-password-Bjmf49i6.js → forgot-password-BNBLi023.js} +1 -1
  36. package/lib/public/assets/{h264Stream-DeD__6wa.js → h264Stream-C51Au2i_.js} +1 -1
  37. package/lib/public/assets/{index-B0gQkEg0.js → index-Dk5J6HB1.js} +2 -2
  38. package/lib/public/assets/{index-B0JeuZbg.js → index-DncSz5LG.js} +1 -1
  39. package/lib/public/assets/{index-72oF89LJ.js → index-Z7_BY3pJ.js} +1 -1
  40. package/lib/public/assets/{input-DIc4MSC7.js → input-bTXM4l09.js} +1 -1
  41. package/lib/public/assets/{line-chart-BcWm2vW8.js → line-chart-B3YmVYyr.js} +1 -1
  42. package/lib/public/assets/{list-checks-B06IJJad.js → list-checks-BClKEj1-.js} +1 -1
  43. package/lib/public/assets/{lock-pVLvnN30.js → lock-CZsenX3D.js} +1 -1
  44. package/lib/public/assets/{login-CN77vy--.js → login-B6A6tOp9.js} +1 -1
  45. package/lib/public/assets/{maintenance-settings-CSdzQSkY.js → maintenance-settings-CmCJGy7O.js} +1 -1
  46. package/lib/public/assets/{monitor-BtrN5IkI.js → monitor-BdTNEglU.js} +1 -1
  47. package/lib/public/assets/{mouse-pointer-2-CNsQfo6w.js → mouse-pointer-2-BM8CaLtt.js} +1 -1
  48. package/lib/public/assets/{network-BSfA8ZxG.js → network-BS6vUc1x.js} +1 -1
  49. package/lib/public/assets/{overview-BfTebCK5.js → overview-Yfk_Y7g3.js} +1 -1
  50. package/lib/public/assets/{page-header-D8KUX6Qg.js → page-header-DhXCwpml.js} +1 -1
  51. package/lib/public/assets/{play-Cvc8xzic.js → play-D08gP1td.js} +1 -1
  52. package/lib/public/assets/{plus-C9Zj84UF.js → plus-BKboorfa.js} +1 -1
  53. package/lib/public/assets/{profile-page-Bi27STgs.js → profile-page-Cd9NodrA.js} +1 -1
  54. package/lib/public/assets/{reset-password-CPsOuwSu.js → reset-password-acLDyyDW.js} +1 -1
  55. package/lib/public/assets/{runbook-page-DEQ-DBOX.js → runbook-page-CWLkC1Fd.js} +1 -1
  56. package/lib/public/assets/{select-BWpnhWXj.js → select-DZoRKNti.js} +1 -1
  57. package/lib/public/assets/{selector-detail-redirect-BefSCSpe.js → selector-detail-redirect-DS4qrt84.js} +1 -1
  58. package/lib/public/assets/{selector-health-page-MSk96Zz7.js → selector-health-page-TpmN61G3.js} +1 -1
  59. package/lib/public/assets/{session-detail-page-DPyBNef_.js → session-detail-page-C0wYaXqy.js} +1 -1
  60. package/lib/public/assets/{settings-V6x-cXwy.js → settings-CxPGEY2j.js} +1 -1
  61. package/lib/public/assets/{stat-tile-Duex-GZD.js → stat-tile-DUpyYgl-.js} +1 -1
  62. package/lib/public/assets/{tablet-BBcoqBmL.js → tablet-SA02BJNQ.js} +1 -1
  63. package/lib/public/assets/{teams-ts8p5rEh.js → teams-jamD0Kxr.js} +1 -1
  64. package/lib/public/assets/{trash-2-CZ2ew6AZ.js → trash-2-DO8fv6w4.js} +1 -1
  65. package/lib/public/assets/{upload-Dv99y_le.js → upload-CaiGCStb.js} +1 -1
  66. package/lib/public/assets/{use-builds-data-DrYC8JCF.js → use-builds-data-jS-fcvVf.js} +1 -1
  67. package/lib/public/assets/{use-password-reset-mode-C7QkBNkP.js → use-password-reset-mode-BzoenegM.js} +1 -1
  68. package/lib/public/assets/{users-gJ5Y01yo.js → users-Bnw7Vs_B.js} +1 -1
  69. package/lib/public/assets/{users-D7tKMESH.js → users-DHvDrriG.js} +1 -1
  70. package/lib/public/assets/{video-off-CqZVuZGs.js → video-off-FEvy0w5U.js} +1 -1
  71. package/lib/public/assets/{webhook-settings-DI5DSW99.js → webhook-settings-CBVB5lxi.js} +1 -1
  72. package/lib/public/assets/{zap-ClcHFxfo.js → zap-BMrTJO6M.js} +1 -1
  73. package/lib/public/index.html +1 -1
  74. package/lib/schema.json +1 -1
  75. package/lib/src/app/index.js +2 -0
  76. package/lib/src/app/openapi/grid.yaml +102 -0
  77. package/lib/src/dashboard/event-manager.js +5 -8
  78. package/lib/src/device-utils.js +11 -2
  79. package/lib/src/gateway/nodeAsk.js +57 -0
  80. package/lib/src/gateway/nodeSessionDeviceLogs.js +35 -0
  81. package/lib/src/plugin.js +4 -1
  82. package/lib/src/scripts/initialize-database.js +0 -6
  83. package/lib/src/scripts/run-migrations.js +123 -880
  84. package/lib/src/services/SessionLifecycleService.js +20 -5
  85. package/lib/src/services/interceptor/CertManager.js +50 -16
  86. package/lib/src/services/logcat/NodeDeviceLogStore.js +99 -0
  87. package/lib/src/services/logcat/NodeDeviceLogsCollector.js +164 -0
  88. package/lib/src/services/logcat/SessionDeviceLogs.js +199 -44
  89. package/lib/src/services/logcat/nodeDeviceLogs.js +91 -0
  90. package/lib/src/services/metrics/nodeMetrics.js +20 -49
  91. package/lib/src/sessions/RemoteSession.js +11 -2
  92. package/lib/src/sessions/SessionManager.js +28 -1
  93. package/lib/test/helpers/fake-logcat.js +80 -0
  94. package/lib/test/helpers/hub-node-pair.js +227 -0
  95. package/lib/test/unit/dashboard-events-device-logs.spec.js +15 -0
  96. package/lib/test/unit/hub-node-iphone-readiness.spec.js +134 -0
  97. package/lib/test/unit/hub-node-session-device-logs.spec.js +194 -0
  98. package/lib/test/unit/hub-node-session-metrics.spec.js +24 -192
  99. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +8 -1
  100. package/lib/test/unit/interceptor/CertManager.spec.js +89 -0
  101. package/lib/test/unit/node-device-log-store.spec.js +102 -0
  102. package/lib/test/unit/node-device-logs-collector.spec.js +343 -0
  103. package/lib/test/unit/node-gateway-create-appium-server.spec.js +8 -1
  104. package/lib/test/unit/node-hub-session-owner.spec.js +8 -1
  105. package/lib/test/unit/node-session-device-logs-route.spec.js +103 -0
  106. package/lib/test/unit/run-migrations-database.spec.js +57 -424
  107. package/lib/test/unit/run-migrations.test.js +74 -674
  108. package/lib/test/unit/session-device-logs-hub-node.spec.js +340 -0
  109. package/lib/test/unit/session-device-logs-lifecycle.spec.js +137 -0
  110. package/lib/test/unit/session-device-logs.spec.js +32 -94
  111. package/lib/test/unit/session-log-indexes.spec.js +15 -0
  112. package/lib/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +1 -1
  114. 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
- void deviceLogs.start({
166
- sessionId: session.getId(),
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 });
@@ -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
- // Principal Health Check Integration: Ensure device is READY before allocation
279
- if (!lockedDevice.cloud) {
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', {