@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.
- package/lib/package.json +1 -1
- package/lib/public/assets/{Layouts-7IT8aFLI.js → Layouts-DyOOhYtz.js} +1 -1
- package/lib/public/assets/{ai-settings-BbnfgdEx.js → ai-settings-UTOAls44.js} +1 -1
- package/lib/public/assets/{apps-CRMrI4_p.js → apps-iWYKDaHG.js} +1 -1
- package/lib/public/assets/{badge-CSvl5xIU.js → badge-DPsMqugr.js} +1 -1
- package/lib/public/assets/{button-CvLaGFYj.js → button-C2nVUsus.js} +1 -1
- package/lib/public/assets/{calendar-6w-D6Oaw.js → calendar-ufCcAAd0.js} +1 -1
- package/lib/public/assets/{clock-DcdeWBPr.js → clock-D9Y3wV_k.js} +1 -1
- package/lib/public/assets/{cpu-DiSoXT9n.js → cpu-HdPsjamK.js} +1 -1
- package/lib/public/assets/device-explorer-QINnmim7.js +195 -0
- package/lib/public/assets/{device-explorer-CxdUAoTL.css → device-explorer-vr8x2iik.css} +1 -1
- package/lib/public/assets/{index-ByQwMN5T.js → index-W_MoQvsw.js} +2 -2
- package/lib/public/assets/{lock-B23ibZmo.js → lock-CJ90SvYX.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CirzA6yG.js → maintenance-settings-CLGqmciU.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-Cz76SHFb.js → mouse-pointer-2-HkRMqHAy.js} +1 -1
- package/lib/public/assets/{plus-BBwlIevt.js → plus-8iOMg9nw.js} +1 -1
- package/lib/public/assets/{session-dashboard-HPDtwPOZ.js → session-dashboard-FUCdTLJc.js} +1 -1
- package/lib/public/assets/{settings-DrZsZwdc.js → settings-DsABv9a7.js} +1 -1
- package/lib/public/assets/{trash-2-DQpvzJec.js → trash-2-g4zyDinF.js} +1 -1
- package/lib/public/assets/{useSocket-Dxsqae2a.js → useSocket-9D3E9Hwl.js} +1 -1
- package/lib/public/assets/{webhook-settings-Cp-B4Nrw.js → webhook-settings-DiEIokQy.js} +1 -1
- package/lib/public/assets/{zap-DovP6iow.js → zap-Dr4fY5UZ.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/device-managers/ios/WDAClient.js +148 -94
- package/package.json +1 -1
- 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
|
-
|
|
805
|
+
if (yield s.isGoIOSAvailable()) {
|
|
728
806
|
command = s.goIOSPath;
|
|
729
807
|
args = ['syslog', '--udid', udid];
|
|
730
808
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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