@xenon-device-management/xenon 1.1.21 → 1.1.23

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 (26) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/public/assets/{Layouts-7IT8aFLI.js → Layouts-DyOOhYtz.js} +1 -1
  3. package/lib/public/assets/{ai-settings-BbnfgdEx.js → ai-settings-UTOAls44.js} +1 -1
  4. package/lib/public/assets/{apps-CRMrI4_p.js → apps-iWYKDaHG.js} +1 -1
  5. package/lib/public/assets/{badge-CSvl5xIU.js → badge-DPsMqugr.js} +1 -1
  6. package/lib/public/assets/{button-CvLaGFYj.js → button-C2nVUsus.js} +1 -1
  7. package/lib/public/assets/{calendar-6w-D6Oaw.js → calendar-ufCcAAd0.js} +1 -1
  8. package/lib/public/assets/{clock-DcdeWBPr.js → clock-D9Y3wV_k.js} +1 -1
  9. package/lib/public/assets/{cpu-DiSoXT9n.js → cpu-HdPsjamK.js} +1 -1
  10. package/lib/public/assets/device-explorer-QINnmim7.js +195 -0
  11. package/lib/public/assets/{device-explorer-CxdUAoTL.css → device-explorer-vr8x2iik.css} +1 -1
  12. package/lib/public/assets/{index-ByQwMN5T.js → index-W_MoQvsw.js} +2 -2
  13. package/lib/public/assets/{lock-B23ibZmo.js → lock-CJ90SvYX.js} +1 -1
  14. package/lib/public/assets/{maintenance-settings-CirzA6yG.js → maintenance-settings-CLGqmciU.js} +1 -1
  15. package/lib/public/assets/{mouse-pointer-2-Cz76SHFb.js → mouse-pointer-2-HkRMqHAy.js} +1 -1
  16. package/lib/public/assets/{plus-BBwlIevt.js → plus-8iOMg9nw.js} +1 -1
  17. package/lib/public/assets/{session-dashboard-HPDtwPOZ.js → session-dashboard-FUCdTLJc.js} +1 -1
  18. package/lib/public/assets/{settings-DrZsZwdc.js → settings-DsABv9a7.js} +1 -1
  19. package/lib/public/assets/{trash-2-DQpvzJec.js → trash-2-g4zyDinF.js} +1 -1
  20. package/lib/public/assets/{useSocket-Dxsqae2a.js → useSocket-9D3E9Hwl.js} +1 -1
  21. package/lib/public/assets/{webhook-settings-Cp-B4Nrw.js → webhook-settings-DiEIokQy.js} +1 -1
  22. package/lib/public/assets/{zap-DovP6iow.js → zap-Dr4fY5UZ.js} +1 -1
  23. package/lib/public/index.html +1 -1
  24. package/lib/src/device-managers/ios/WDAClient.js +148 -94
  25. package/package.json +1 -1
  26. package/lib/public/assets/device-explorer-CajM63OJ.js +0 -193
@@ -17,6 +17,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
17
17
  var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
+ var WDAClient_1;
20
21
  Object.defineProperty(exports, "__esModule", { value: true });
21
22
  exports.WDAClient = void 0;
22
23
  const axios_1 = __importDefault(require("axios"));
@@ -33,13 +34,15 @@ const os_1 = __importDefault(require("os"));
33
34
  const fs_extra_1 = __importDefault(require("fs-extra"));
34
35
  const execFilePromise = (0, util_1.promisify)(child_process_1.execFile);
35
36
  const execPromise = (0, util_1.promisify)(child_process_1.exec);
36
- let WDAClient = class WDAClient {
37
+ let WDAClient = WDAClient_1 = class WDAClient {
37
38
  constructor() {
38
39
  this.log = logger_1.default.scope('WDAClient');
39
40
  this.wdaConnectionCache = new Map();
40
41
  this.commandQueues = new Map();
41
42
  this.ideviceinstallerVersion = null;
42
43
  this.typeBuffers = new Map();
44
+ // Persistent log streams: one background syslog process per device
45
+ this.logStreams = new Map();
43
46
  }
44
47
  executeSerializedCommand(udid, action) {
45
48
  return __awaiter(this, void 0, void 0, function* () {
@@ -712,97 +715,114 @@ let WDAClient = class WDAClient {
712
715
  }
713
716
  });
714
717
  }
718
+ startLogStream(udid, command, args, goIOSPath) {
719
+ var _a;
720
+ // Already running?
721
+ const existing = this.logStreams.get(udid);
722
+ if (existing && existing.proc && !existing.proc.killed) {
723
+ existing.lastAccess = Date.now();
724
+ return;
725
+ }
726
+ this.log.info(`[LogStream] Starting persistent syslog for ${udid}: ${command} ${args.join(' ')}`);
727
+ const proc = (0, child_process_1.spawn)(command, args, {
728
+ env: Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }),
729
+ stdio: ['ignore', 'pipe', 'pipe'],
730
+ });
731
+ const entry = {
732
+ proc,
733
+ buffer: [],
734
+ lastAccess: Date.now(),
735
+ idleTimer: null,
736
+ goIOSPath,
737
+ };
738
+ if (proc.stdout) {
739
+ const rl = readline_1.default.createInterface({ input: proc.stdout, terminal: false });
740
+ rl.on('line', (line) => {
741
+ if (!line.trim())
742
+ return;
743
+ let parsed = line;
744
+ if (command === goIOSPath) {
745
+ try {
746
+ const obj = JSON.parse(line);
747
+ parsed = obj.msg || line;
748
+ }
749
+ catch (e) {
750
+ // Use raw line
751
+ }
752
+ }
753
+ entry.buffer.push(parsed);
754
+ // Ring buffer: drop oldest lines
755
+ if (entry.buffer.length > WDAClient_1.LOG_BUFFER_MAX) {
756
+ entry.buffer.splice(0, entry.buffer.length - WDAClient_1.LOG_BUFFER_MAX);
757
+ }
758
+ });
759
+ }
760
+ // Suppress stderr noise (go-ios tunnel messages)
761
+ (_a = proc.stderr) === null || _a === void 0 ? void 0 : _a.on('data', () => { });
762
+ proc.on('error', (err) => {
763
+ this.log.debug(`[LogStream] Process error for ${udid}: ${err.message}`);
764
+ this.logStreams.delete(udid);
765
+ });
766
+ proc.on('exit', (code) => {
767
+ this.log.debug(`[LogStream] Process exited for ${udid} with code ${code}`);
768
+ this.logStreams.delete(udid);
769
+ });
770
+ this.logStreams.set(udid, entry);
771
+ }
772
+ stopLogStream(udid) {
773
+ const entry = this.logStreams.get(udid);
774
+ if (entry) {
775
+ if (entry.idleTimer)
776
+ clearTimeout(entry.idleTimer);
777
+ if (entry.proc && !entry.proc.killed) {
778
+ entry.proc.kill('SIGKILL');
779
+ }
780
+ this.logStreams.delete(udid);
781
+ this.log.debug(`[LogStream] Stopped persistent syslog for ${udid}`);
782
+ }
783
+ }
715
784
  getLogs(udid) {
716
785
  return __awaiter(this, void 0, void 0, function* () {
717
786
  const s = typedi_1.Container.get(IOSStreamService_1.default);
718
- // Find device to check if it's a simulator
719
787
  const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
720
788
  const isSimulator = device && !device.realDevice;
721
789
  let command = 'idevicesyslog';
722
790
  let args = ['-u', udid];
723
791
  if (isSimulator) {
792
+ // Simulators: Use one-shot log show (no persistent stream needed)
724
793
  command = 'xcrun';
725
794
  args = ['simctl', 'spawn', udid, 'log', 'show', '--last', '10s', '--style', 'compact'];
795
+ return new Promise((resolve) => {
796
+ var _a;
797
+ const proc = (0, child_process_1.spawn)(command, args);
798
+ let output = '';
799
+ (_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => { output += data.toString(); });
800
+ proc.on('close', () => resolve(output));
801
+ setTimeout(() => { if (!proc.killed)
802
+ proc.kill('SIGKILL'); resolve(output); }, 5000);
803
+ });
726
804
  }
727
- else if (yield s.isGoIOSAvailable()) {
805
+ if (yield s.isGoIOSAvailable()) {
728
806
  command = s.goIOSPath;
729
807
  args = ['syslog', '--udid', udid];
730
808
  }
731
- this.log.debug(`Fetching logs for ${udid} (Simulator: ${isSimulator}) using ${command} ${args.join(' ')}`);
732
- return new Promise((resolve) => {
733
- var _a;
734
- const proc = (0, child_process_1.spawn)(command, args, {
735
- env: Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }),
736
- });
737
- let output = '';
738
- let resolved = false;
739
- if (!proc.stdout) {
740
- resolved = true;
741
- this.log.error(`Failed to capture logs for ${udid}: stdout stream is missing`);
742
- proc.kill('SIGKILL');
743
- resolve(output);
744
- return;
745
- }
746
- const rl = readline_1.default.createInterface({
747
- input: proc.stdout,
748
- terminal: false,
749
- });
750
- let timer;
751
- const resetInactivityTimeout = () => {
752
- if (timer)
753
- clearTimeout(timer);
754
- timer = setTimeout(() => {
755
- if (!resolved) {
756
- resolved = true;
757
- rl.close();
758
- proc.kill('SIGKILL');
759
- this.log.debug(`getLogs snapshot completed (inactivity timeout) for ${udid}`);
760
- resolve(output);
761
- }
762
- }, 2000);
763
- };
764
- // Initial timer start
765
- resetInactivityTimeout();
766
- rl.on('line', (line) => {
767
- resetInactivityTimeout();
768
- if (!line.trim())
769
- return;
770
- if (command === s.goIOSPath) {
771
- try {
772
- const parsed = JSON.parse(line);
773
- output += (parsed.msg || line) + '\n';
774
- }
775
- catch (e) {
776
- output += line + '\n';
777
- }
778
- }
779
- else {
780
- output += line + '\n';
781
- }
782
- });
783
- (_a = proc.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
784
- resetInactivityTimeout();
785
- this.log.debug(`[getLogs][stderr] ${data.toString()}`);
786
- });
787
- proc.on('error', (err) => {
788
- if (!resolved) {
789
- resolved = true;
790
- rl.close();
791
- this.log.debug(`getLogs failed for ${udid}: ${err.message}`);
792
- clearTimeout(timer);
793
- resolve(output);
794
- }
795
- });
796
- proc.on('exit', (code) => {
797
- if (!resolved) {
798
- resolved = true;
799
- rl.close();
800
- clearTimeout(timer);
801
- this.log.debug(`getLogs process exited with code ${code} for ${udid}`);
802
- resolve(output);
803
- }
804
- });
805
- });
809
+ // Ensure persistent stream is running
810
+ this.startLogStream(udid, command, args, s.goIOSPath);
811
+ const entry = this.logStreams.get(udid);
812
+ if (!entry) {
813
+ return '';
814
+ }
815
+ // Mark access and reset idle timer
816
+ entry.lastAccess = Date.now();
817
+ if (entry.idleTimer)
818
+ clearTimeout(entry.idleTimer);
819
+ entry.idleTimer = setTimeout(() => {
820
+ this.log.info(`[LogStream] Idle timeout for ${udid}, stopping persistent syslog`);
821
+ this.stopLogStream(udid);
822
+ }, WDAClient_1.LOG_IDLE_TIMEOUT_MS);
823
+ // Return buffered lines and clear (so next poll gets only new lines)
824
+ const lines = entry.buffer.splice(0, entry.buffer.length);
825
+ return lines.join('\n');
806
826
  });
807
827
  }
808
828
  verifyWDAStatus(udid) {
@@ -838,29 +858,63 @@ let WDAClient = class WDAClient {
838
858
  }
839
859
  executeShell(udid, command) {
840
860
  return __awaiter(this, void 0, void 0, function* () {
841
- const ALLOWED_COMMANDS = ['ls', 'ps', 'top', 'whoami', 'date', 'uptime', 'netstat', 'id'];
842
- // Basic sanitation
861
+ // Separate allowlists for simulators vs real devices
862
+ const SIMCTL_COMMANDS = ['listapps', 'get_app_container', 'list', 'getenv'];
863
+ const GOIOS_COMMANDS = ['apps', 'info', 'syslog', 'list', 'deviceinfo', 'diagnostics'];
864
+ const GENERIC_COMMANDS = ['ls', 'ps', 'top', 'whoami', 'date', 'uptime', 'netstat', 'id'];
843
865
  const safeCommand = command.trim();
844
- // Check if the command starts with any allowed prefix
845
- const isAllowed = ALLOWED_COMMANDS.some((prefix) => safeCommand.startsWith(prefix));
846
- if (!isAllowed) {
847
- this.log.warn(`Blocked potentially unsafe shell command on ${udid}: ${safeCommand}`);
866
+ const commandWord = safeCommand.split(/\s+/)[0]; // First word is the command
867
+ // Determine device type
868
+ const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
869
+ const isSimulator = device && !device.realDevice;
870
+ // Check allowlists based on device type
871
+ const isSimctlAllowed = SIMCTL_COMMANDS.includes(commandWord);
872
+ const isGoiosAllowed = GOIOS_COMMANDS.includes(commandWord);
873
+ const isGenericAllowed = GENERIC_COMMANDS.some((prefix) => safeCommand.startsWith(prefix));
874
+ if (!isSimctlAllowed && !isGoiosAllowed && !isGenericAllowed) {
875
+ this.log.warn(`Blocked shell command on ${udid}: ${safeCommand}`);
848
876
  throw new Error(`Command '${safeCommand}' is not allowed for security reasons.`);
849
877
  }
850
- // Split command into args for execFilePromise
851
878
  const args = safeCommand.split(/\s+/);
852
- const { stdout } = yield execFilePromise('xcrun', ['simctl', ...args, udid]).catch((e) => __awaiter(this, void 0, void 0, function* () {
853
- this.log.debug(`xcrun simctl failed for ${udid}: ${e.message}. Trying go-ios fallback.`);
854
- const s = typedi_1.Container.get(IOSStreamService_1.default);
855
- return yield execFilePromise(s.goIOSPath, [...args, '--udid', udid], {
856
- env: Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }),
857
- });
858
- }));
859
- return stdout;
879
+ const s = typedi_1.Container.get(IOSStreamService_1.default);
880
+ // Route to the correct tool
881
+ if (isSimctlAllowed && isSimulator) {
882
+ // Simulator-specific: xcrun simctl <command> <udid> [args...]
883
+ const [cmd, ...rest] = args;
884
+ const { stdout } = yield execFilePromise('xcrun', ['simctl', cmd, udid, ...rest]);
885
+ return stdout;
886
+ }
887
+ if (isGoiosAllowed) {
888
+ // Real device: go-ios <command> --udid <udid> [args...]
889
+ if (yield s.isGoIOSAvailable()) {
890
+ const { stdout } = yield execFilePromise(s.goIOSPath, [...args, '--udid', udid], {
891
+ env: Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }),
892
+ });
893
+ return stdout;
894
+ }
895
+ throw new Error(`Command '${commandWord}' requires go-ios but it is not available.`);
896
+ }
897
+ if (isSimctlAllowed && !isSimulator) {
898
+ // User tried a simctl command on a real device - give helpful error
899
+ throw new Error(`Command '${commandWord}' is only available for Simulators. For real devices, try: ${GOIOS_COMMANDS.join(', ')}`);
900
+ }
901
+ // Generic system command - try simctl first, fallback to go-ios
902
+ if (isSimulator) {
903
+ const { stdout } = yield execFilePromise('xcrun', ['simctl', 'spawn', udid, ...args]);
904
+ return stdout;
905
+ }
906
+ else if (yield s.isGoIOSAvailable()) {
907
+ // For real devices, generic commands aren't directly possible via go-ios
908
+ // Try go-ios launch/exec if available, otherwise inform user
909
+ throw new Error(`System command '${commandWord}' is not available on real devices. Use device-specific commands: ${GOIOS_COMMANDS.join(', ')}`);
910
+ }
911
+ throw new Error(`Could not execute command: no suitable runtime found for ${isSimulator ? 'simulator' : 'real device'}`);
860
912
  });
861
913
  }
862
914
  };
863
915
  exports.WDAClient = WDAClient;
864
- exports.WDAClient = WDAClient = __decorate([
916
+ WDAClient.LOG_BUFFER_MAX = 500; // Ring buffer size
917
+ WDAClient.LOG_IDLE_TIMEOUT_MS = 60000; // Kill after 60s of no polling
918
+ exports.WDAClient = WDAClient = WDAClient_1 = __decorate([
865
919
  (0, typedi_1.Service)()
866
920
  ], WDAClient);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {