@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.
Files changed (223) hide show
  1. package/README.md +11 -5
  2. package/lib/package.json +1 -1
  3. package/lib/public/assets/{AnnotationOverlay-Cvpkk4r9.js → AnnotationOverlay-B_VNtKBH.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-Bhn5ZtVM.js → ApiKeyGate-BwuaMjkX.js} +1 -1
  5. package/lib/public/assets/{BugReportButton-BA6aEjTC.js → BugReportButton-BAt0_pzq.js} +1 -1
  6. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +2 -0
  7. package/lib/public/assets/{EmptyState-CgzbuUvO.js → EmptyState-BSjhk_IV.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BHc_5qNc.js → FieldGroup-Cb5QZsE1.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-D7OMGuRc.js → FilterMenu-BAaSlWbW.js} +1 -1
  10. package/lib/public/assets/{Menu-C173JJ0l.js → Menu-JGGYrSQQ.js} +1 -1
  11. package/lib/public/assets/{Modal-ChvVUI3B.js → Modal-CFCp3n9d.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-Dhw4d_O6.js → RecordingPage-CbJj_EBT.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-z97ftGK-.js → RecordingsPage-BJeYnygR.js} +2 -2
  14. package/lib/public/assets/{SegmentedControl-DbXhBGZC.js → SegmentedControl-Bg42Atjy.js} +1 -1
  15. package/lib/public/assets/{SettingCard-C908GgiI.js → SettingCard-Njz1pUIJ.js} +1 -1
  16. package/lib/public/assets/{Table-B1DB_ZN-.js → Table-BcTm5xV2.js} +1 -1
  17. package/lib/public/assets/{activity-Css0xNvp.js → activity-DOVdsfMQ.js} +1 -1
  18. package/lib/public/assets/{ai-settings-BMQK3-yN.js → ai-settings-CsuJroRf.js} +2 -2
  19. package/lib/public/assets/{api-keys-CbJ42eDQ.js → api-keys-BKnNSTw9.js} +1 -1
  20. package/lib/public/assets/{apps-DW1pw2re.js → apps-D0SC6uw6.js} +2 -2
  21. package/lib/public/assets/{arrow-left-Dpog9L0F.js → arrow-left-MMbTmgoE.js} +1 -1
  22. package/lib/public/assets/{arrow-right-CsBIlSIw.js → arrow-right-BMfnGmxW.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-z1tRQE4R.js → arrow-up-right-DuAftMWB.js} +1 -1
  24. package/lib/public/assets/{auth-shell-mXiX5vNk.js → auth-shell-nZC2JNDO.js} +2 -2
  25. package/lib/public/assets/{builds-page-BcFn23bE.js → builds-page-hDCgTwkS.js} +1 -1
  26. package/lib/public/assets/{button-Cq_zHETv.js → button-Dda4irn_.js} +1 -1
  27. package/lib/public/assets/{calendar-CKxABX9q.js → calendar-BMr0Q62v.js} +1 -1
  28. package/lib/public/assets/{check-C7xu-hGU.js → check-CZ9iw9-e.js} +1 -1
  29. package/lib/public/assets/{chevron-right-BiqfdCGA.js → chevron-right-BRYJJVC9.js} +1 -1
  30. package/lib/public/assets/{circle-check-CyBjjPxR.js → circle-check-CqAJjQ5O.js} +1 -1
  31. package/lib/public/assets/{circle-x-6gMRcx6z.js → circle-x-DlDJtnVz.js} +1 -1
  32. package/lib/public/assets/{clock-U1rDTT1h.js → clock-CpuDKysU.js} +1 -1
  33. package/lib/public/assets/{copy-BIM0ZX0L.js → copy-CAQydNFe.js} +1 -1
  34. package/lib/public/assets/device-explorer-CdO6_nrn.js +282 -0
  35. package/lib/public/assets/{download-DDBRXnAZ.js → download-C9XG6lLv.js} +1 -1
  36. package/lib/public/assets/{forgot-password-Dpg7yxM8.js → forgot-password-CGnkRgE9.js} +1 -1
  37. package/lib/public/assets/h264Stream-VQBphYhg.js +26 -0
  38. package/lib/public/assets/{index-BGBqohfQ.js → index-BbKEXUFH.js} +5 -5
  39. package/lib/public/assets/{index-BTYMAqNW.css → index-Bxoxt9sV.css} +1 -1
  40. package/lib/public/assets/{index-BQ2luT7L.js → index-DJGc1M2f.js} +1 -1
  41. package/lib/public/assets/{input-BvB1cjHE.js → input-Cm0tmSoc.js} +1 -1
  42. package/lib/public/assets/{line-chart-m78VggnW.js → line-chart-D2DyJCml.js} +1 -1
  43. package/lib/public/assets/{list-checks-cY7o1QTL.js → list-checks-UWf1GY99.js} +1 -1
  44. package/lib/public/assets/{lock-loz8txQm.js → lock-CCJv9m_I.js} +1 -1
  45. package/lib/public/assets/{login-BYOvz4lx.js → login-f8vOVW-U.js} +1 -1
  46. package/lib/public/assets/maintenance-settings-C1PrkuAH.js +6 -0
  47. package/lib/public/assets/{monitor-C4HPHb6s.js → monitor-B2jDgwm7.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-BkdKtCzi.js → mouse-pointer-2-DLI1RtzU.js} +1 -1
  49. package/lib/public/assets/{network-C5e0MIWo.js → network-ebT86qoi.js} +1 -1
  50. package/lib/public/assets/{overview-tlu8ijGd.js → overview-C0aykVrd.js} +1 -1
  51. package/lib/public/assets/{page-header-FXPlo6az.js → page-header-BjzgWz-T.js} +1 -1
  52. package/lib/public/assets/{play-CWdk0_Sy.js → play-DLG9t8FU.js} +1 -1
  53. package/lib/public/assets/{plus-B2xCnntV.js → plus-nBFSMu_X.js} +1 -1
  54. package/lib/public/assets/{profile-page-D080qzr4.js → profile-page-CSl0UYlv.js} +1 -1
  55. package/lib/public/assets/{reset-password-D3u4Mbvn.js → reset-password-LeOuHqmB.js} +1 -1
  56. package/lib/public/assets/{runbook-page-hPRZMy7s.js → runbook-page-CMMeQml9.js} +1 -1
  57. package/lib/public/assets/{select-12RThAXc.js → select-88jHcl-Z.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-uQwvV7EQ.js → selector-detail-redirect-BbXKJjZM.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-CThoxDFO.js → selector-health-page-B4rA1Xkv.js} +2 -2
  60. package/lib/public/assets/session-detail-page-COL_RZxw.js +4 -0
  61. package/lib/public/assets/settings-GF6ZMSQ6.js +6 -0
  62. package/lib/public/assets/{stat-tile-B02WEte8.js → stat-tile-BV98e_Sv.js} +1 -1
  63. package/lib/public/assets/{tablet-Dj30VGZl.js → tablet-BiAsnFDj.js} +1 -1
  64. package/lib/public/assets/{teams-uL4fjTDR.js → teams-B4-jrr-1.js} +1 -1
  65. package/lib/public/assets/{trash-2-B54e-TfY.js → trash-2-C5RSuX0e.js} +1 -1
  66. package/lib/public/assets/{upload-B3k8WCSP.js → upload-BG7_rMXK.js} +1 -1
  67. package/lib/public/assets/{use-builds-data-BFn90NH3.js → use-builds-data-J-riQYyC.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-DL2TmWZ0.js → use-password-reset-mode-C8-eH-UG.js} +1 -1
  69. package/lib/public/assets/{users-CqBuxMlX.js → users-CFba5TSy.js} +1 -1
  70. package/lib/public/assets/{users-DjQSucKb.js → users-Cp0beJm3.js} +1 -1
  71. package/lib/public/assets/{video-off-T0rlt0GA.js → video-off-DDsEISjV.js} +1 -1
  72. package/lib/public/assets/webhook-settings-BGu32vSB.js +1 -0
  73. package/lib/public/assets/{zap-gBHqutZV.js → zap-l4VaXzq8.js} +1 -1
  74. package/lib/public/index.html +2 -2
  75. package/lib/schema.json +19 -46
  76. package/lib/src/XenonCapabilityManager.js +22 -5
  77. package/lib/src/app/openapi/control.yaml +71 -20
  78. package/lib/src/app/openapi/grid.yaml +23 -5
  79. package/lib/src/app/openapi/platform.yaml +136 -28
  80. package/lib/src/app/routers/control.js +30 -5
  81. package/lib/src/app/routers/dashboard.js +29 -1
  82. package/lib/src/app/routers/grid.js +32 -10
  83. package/lib/src/app/routers/streamStartConflict.js +3 -0
  84. package/lib/src/app/routers/streamType.js +7 -2
  85. package/lib/src/app/routers/webhook.js +8 -4
  86. package/lib/src/config.js +50 -5
  87. package/lib/src/dashboard/commands.js +185 -141
  88. package/lib/src/dashboard/event-manager.js +39 -6
  89. package/lib/src/data-service/appleFamily.js +43 -0
  90. package/lib/src/data-service/db.js +11 -0
  91. package/lib/src/data-service/device-service.js +46 -1
  92. package/lib/src/data-service/device-store.js +66 -3
  93. package/lib/src/data-service/deviceClaims.js +37 -0
  94. package/lib/src/data-service/deviceFieldOwners.js +25 -11
  95. package/lib/src/data-service/deviceSettings.js +79 -0
  96. package/lib/src/data-service/prisma-store.js +152 -9
  97. package/lib/src/data-service/web-config-service.js +43 -92
  98. package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
  99. package/lib/src/device-managers/ios/WDAClient.js +44 -43
  100. package/lib/src/device-managers/shellCommands.js +96 -0
  101. package/lib/src/device-utils.js +143 -37
  102. package/lib/src/generated/client/edge.js +17 -3
  103. package/lib/src/generated/client/index-browser.js +14 -0
  104. package/lib/src/generated/client/index.d.ts +2054 -379
  105. package/lib/src/generated/client/index.js +17 -3
  106. package/lib/src/generated/client/package.json +1 -1
  107. package/lib/src/generated/client/schema.prisma +35 -7
  108. package/lib/src/generated/client/wasm.js +14 -0
  109. package/lib/src/interceptors/CommandInterceptor.js +29 -8
  110. package/lib/src/interceptors/xenonScripts.js +55 -0
  111. package/lib/src/interfaces/IPluginArgs.js +1 -2
  112. package/lib/src/middleware/deviceAccessGuard.js +63 -9
  113. package/lib/src/plugin.js +15 -1
  114. package/lib/src/prisma.js +1 -1
  115. package/lib/src/scripts/database-check.js +124 -0
  116. package/lib/src/services/AIService.js +99 -0
  117. package/lib/src/services/CleanupService.js +6 -1
  118. package/lib/src/services/InterceptorService.js +70 -13
  119. package/lib/src/services/NetworkConditioningService.js +85 -88
  120. package/lib/src/services/NotificationService.js +36 -29
  121. package/lib/src/services/OrphanSweeper.js +5 -0
  122. package/lib/src/services/PortAllocator.js +5 -0
  123. package/lib/src/services/ServerManager.js +97 -8
  124. package/lib/src/services/SessionLifecycleService.js +63 -53
  125. package/lib/src/services/ShutdownCoordinator.js +33 -0
  126. package/lib/src/services/TeamService.js +20 -3
  127. package/lib/src/services/device-access/SessionOwnerResolver.js +26 -0
  128. package/lib/src/services/device-access/deviceAccessPolicy.js +38 -6
  129. package/lib/src/services/device-access/leaseHold.js +26 -0
  130. package/lib/src/services/device-access/ticketActorAccess.js +6 -0
  131. package/lib/src/services/healing/HealingOrchestrator.js +32 -1
  132. package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
  133. package/lib/src/services/interceptor/interceptorOptions.js +44 -0
  134. package/lib/src/services/lease/activeLeases.js +37 -0
  135. package/lib/src/services/network/AndroidRadios.js +71 -0
  136. package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
  137. package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
  138. package/lib/src/services/network/adbForPhone.js +60 -0
  139. package/lib/src/services/network/phoneNetworkLock.js +20 -0
  140. package/lib/src/services/network/xenonProxy.js +107 -0
  141. package/lib/src/services/omni-vision/OmniVisionService.js +53 -43
  142. package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
  143. package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
  144. package/lib/src/services/omni-vision/screenScale.js +68 -0
  145. package/lib/src/services/recording/busy-precheck.js +24 -3
  146. package/lib/src/services/recording/concurrency-gate.js +12 -3
  147. package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
  148. package/lib/src/services/settings/labSettings.js +149 -0
  149. package/lib/src/services/webhookEvents.js +173 -0
  150. package/lib/test/helpers/fake-adb.js +58 -0
  151. package/lib/test/helpers/fake-device-table.js +63 -24
  152. package/lib/test/helpers/scratch-database.js +1 -0
  153. package/lib/test/unit/TeamService.test.js +3 -0
  154. package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
  155. package/lib/test/unit/apple-family.spec.js +36 -0
  156. package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
  157. package/lib/test/unit/cleanup-settings.spec.js +244 -0
  158. package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
  159. package/lib/test/unit/dashboard-commands.spec.js +249 -0
  160. package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
  161. package/lib/test/unit/database-check.spec.js +108 -0
  162. package/lib/test/unit/default-plugin-args.spec.js +33 -0
  163. package/lib/test/unit/device-access-lease.spec.js +270 -0
  164. package/lib/test/unit/device-offline-webhook.spec.js +81 -0
  165. package/lib/test/unit/device-setting-columns.spec.js +66 -0
  166. package/lib/test/unit/device-settings-loki.spec.js +87 -0
  167. package/lib/test/unit/device-settings-survive.spec.js +555 -0
  168. package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
  169. package/lib/test/unit/emulator-boot.spec.js +91 -0
  170. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +48 -0
  171. package/lib/test/unit/hub-node-same-host.spec.js +5 -0
  172. package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
  173. package/lib/test/unit/json-logging-option.spec.js +74 -0
  174. package/lib/test/unit/lab-settings.spec.js +218 -0
  175. package/lib/test/unit/lease/allocate-lease-branch.spec.js +22 -0
  176. package/lib/test/unit/lease/session-lease-proof.spec.js +59 -0
  177. package/lib/test/unit/lease-held-phone.spec.js +196 -0
  178. package/lib/test/unit/max-sessions.spec.js +240 -0
  179. package/lib/test/unit/ocr-text-match.spec.js +44 -0
  180. package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
  181. package/lib/test/unit/omni-vision-ios-points.spec.js +161 -0
  182. package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
  183. package/lib/test/unit/omni-vision.spec.js +1 -1
  184. package/lib/test/unit/partial-config-file.spec.js +114 -0
  185. package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
  186. package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
  187. package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
  188. package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
  189. package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
  190. package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
  191. package/lib/test/unit/phone-network/session-start.spec.js +110 -0
  192. package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
  193. package/lib/test/unit/plugin.spec.js +3 -3
  194. package/lib/test/unit/recording-options.spec.js +181 -0
  195. package/lib/test/unit/schema-required-args.spec.js +21 -16
  196. package/lib/test/unit/self-healing-boot.spec.js +75 -0
  197. package/lib/test/unit/self-healing-setting.spec.js +272 -0
  198. package/lib/test/unit/session-failed-webhook.spec.js +170 -0
  199. package/lib/test/unit/shell-commands.spec.js +133 -0
  200. package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
  201. package/lib/test/unit/stream-start-pipelines.spec.js +210 -0
  202. package/lib/test/unit/stream-status-type.spec.js +4 -0
  203. package/lib/test/unit/vision-assertion.spec.js +118 -0
  204. package/lib/test/unit/wda-client-shell.spec.js +133 -0
  205. package/lib/test/unit/web-config-service.spec.js +79 -0
  206. package/lib/test/unit/webhook-delivery.spec.js +24 -0
  207. package/lib/test/unit/webhook-payloads.spec.js +273 -0
  208. package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
  209. package/lib/tsconfig.tsbuildinfo +1 -1
  210. package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
  211. package/package.json +1 -1
  212. package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
  213. package/prisma/schema.prisma +35 -7
  214. package/schema.json +19 -46
  215. package/scripts/generate-types-from-schema.js +7 -2
  216. package/scripts/lib/always-present-plugin-args.js +42 -0
  217. package/lib/public/assets/DeviceMosaicView-ChhHnpFQ.js +0 -2
  218. package/lib/public/assets/device-explorer-DSpAA_t9.js +0 -279
  219. package/lib/public/assets/maintenance-settings-BxbA7SkB.js +0 -6
  220. package/lib/public/assets/recording-group-store-B75435xR.js +0 -26
  221. package/lib/public/assets/session-detail-page-Bc02YjMm.js +0 -4
  222. package/lib/public/assets/settings-C1VIluuT.js +0 -6
  223. 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
- const result = yield provider.heal(context);
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
+ }