@xenon-device-management/xenon 1.1.16 → 1.1.18
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
CHANGED
package/lib/src/app/index.js
CHANGED
|
@@ -140,6 +140,7 @@ const findPublicPath = () => {
|
|
|
140
140
|
path_1.default.resolve(__dirname, '../public'), // Alternative structure
|
|
141
141
|
path_1.default.resolve(__dirname, '../../public'),
|
|
142
142
|
path_1.default.resolve(__dirname, '../../../public'),
|
|
143
|
+
path_1.default.resolve(__dirname, '../../../../public'),
|
|
143
144
|
];
|
|
144
145
|
for (const p of searchPaths) {
|
|
145
146
|
if (fs_1.default.existsSync(path_1.default.join(p, 'index.html'))) {
|
|
@@ -154,9 +155,16 @@ const findPublicPath = () => {
|
|
|
154
155
|
};
|
|
155
156
|
const publicPath = findPublicPath();
|
|
156
157
|
logger_1.default.info(`[Xenon] Public assets path resolved to: ${publicPath}`);
|
|
157
|
-
|
|
158
|
+
// Principal Security: Add permissive CSP and CORS for the dashboard
|
|
159
|
+
router.use((req, res, next) => {
|
|
160
|
+
res.setHeader('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; frame-ancestors 'self';");
|
|
161
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
162
|
+
next();
|
|
163
|
+
});
|
|
164
|
+
staticFilesRouter.use(express_1.default.static(publicPath, { index: false }));
|
|
158
165
|
router.use('/api', apiRouter);
|
|
159
|
-
|
|
166
|
+
// Principal Fix: Rename collision route from /assets to /session-recordings to avoid conflict with dashboard's /assets folder
|
|
167
|
+
router.use('/session-recordings', express_1.default.static(config_1.config.sessionAssetsPath));
|
|
160
168
|
router.use(staticFilesRouter);
|
|
161
169
|
function createRouter(pluginArgs) {
|
|
162
170
|
dashboard_1.default.register(apiRouter);
|
|
@@ -184,13 +192,32 @@ function createRouter(pluginArgs) {
|
|
|
184
192
|
});
|
|
185
193
|
// Fallback route for client-side routing - serve index.html for all non-API routes
|
|
186
194
|
// MUST be registered after Swagger to avoid interception
|
|
187
|
-
router.get(
|
|
188
|
-
const indexPath = path_1.default.
|
|
195
|
+
router.get('*', (req, res) => {
|
|
196
|
+
const indexPath = path_1.default.resolve(publicPath, 'index.html');
|
|
197
|
+
const url = req.originalUrl || req.url;
|
|
198
|
+
// Skip if it's an API call that somehow reached here
|
|
199
|
+
if (url.includes('/api/'))
|
|
200
|
+
return res.status(404).json({ error: true, message: 'Not Found' });
|
|
201
|
+
logger_1.default.debug(`[Xenon] UI Fallback triggered for: ${url}. Targeting: ${indexPath}`);
|
|
189
202
|
if (fs_1.default.existsSync(indexPath)) {
|
|
190
|
-
res.
|
|
203
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
204
|
+
res.sendFile(indexPath, (err) => {
|
|
205
|
+
if (err && !res.headersSent) {
|
|
206
|
+
logger_1.default.error(`[Xenon] res.sendFile failed for ${indexPath}. Error: ${err.message}`);
|
|
207
|
+
res.status(404).send(`Xenon UI Asset Error: ${err.message}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
191
210
|
}
|
|
192
211
|
else {
|
|
193
212
|
logger_1.default.error(`[Xenon] UI Fallback failed: index.html not found at ${indexPath}`);
|
|
213
|
+
// Diagnostic: List files in publicPath
|
|
214
|
+
try {
|
|
215
|
+
const files = fs_1.default.readdirSync(publicPath);
|
|
216
|
+
logger_1.default.error(`[Xenon] Contents of ${publicPath}: ${files.join(', ')}`);
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
logger_1.default.error(`[Xenon] Could not even read directory ${publicPath}: ${e.message}`);
|
|
220
|
+
}
|
|
194
221
|
res.status(404).send('Xenon UI assets not found. Check installation.');
|
|
195
222
|
}
|
|
196
223
|
});
|
|
@@ -179,12 +179,18 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
179
179
|
: adbInstance.adbRemoteHost;
|
|
180
180
|
const existingDevice = existingDeviceDetails.find((dev) => dev.udid === device.udid && dev.host.includes(this.pluginArgs.bindHostOrIp));
|
|
181
181
|
if (existingDevice) {
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (device.state === 'device') {
|
|
183
|
+
logger_1.default.debug(`Android Device details for ${device.udid} already available and online`);
|
|
184
|
+
return Object.assign(Object.assign({}, existingDevice), { state: 'device', busy: existingDevice.busy || false });
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
logger_1.default.info(`Device ${device.udid} was cached but is now in "${device.state}" state. Ignoring.`);
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
184
190
|
}
|
|
185
191
|
else {
|
|
186
|
-
logger_1.default.info(`Android Device details for ${device.udid} not available. So querying now.`);
|
|
187
192
|
if (device.state === 'device') {
|
|
193
|
+
logger_1.default.info(`New Android Device ${device.udid} discovered in "device" state. Querying details...`);
|
|
188
194
|
try {
|
|
189
195
|
return yield this.deviceInfo(device, adbInstance, this.pluginArgs, this.hostPort);
|
|
190
196
|
}
|
|
@@ -194,7 +200,7 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
194
200
|
}
|
|
195
201
|
}
|
|
196
202
|
else {
|
|
197
|
-
logger_1.default.info(`Device ${device.udid} is
|
|
203
|
+
logger_1.default.info(`Device ${device.udid} is in "${device.state}" state. Ignoring.`);
|
|
198
204
|
return undefined;
|
|
199
205
|
}
|
|
200
206
|
}
|
package/lib/src/device-utils.js
CHANGED
|
@@ -66,6 +66,7 @@ exports.unblockCandidateDevices = unblockCandidateDevices;
|
|
|
66
66
|
exports.releaseBlockedDevices = releaseBlockedDevices;
|
|
67
67
|
exports.setupCronReleaseBlockedDevices = setupCronReleaseBlockedDevices;
|
|
68
68
|
exports.setupCronUpdateDeviceList = setupCronUpdateDeviceList;
|
|
69
|
+
exports.setupCronLocalDiscovery = setupCronLocalDiscovery;
|
|
69
70
|
exports.cleanPendingSessions = cleanPendingSessions;
|
|
70
71
|
exports.setupCronCleanPendingSessions = setupCronCleanPendingSessions;
|
|
71
72
|
exports.setupCronCleanExpiredReservations = setupCronCleanExpiredReservations;
|
|
@@ -578,6 +579,21 @@ function setupCronUpdateDeviceList(host, hubArgument, intervalMs, tlsRejectUnaut
|
|
|
578
579
|
}), intervalMs);
|
|
579
580
|
});
|
|
580
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Principal discovery: Periodically poll for local devices to prune stales/offlines.
|
|
584
|
+
* Critical for standalone Hubs where setupCronUpdateDeviceList isn't called.
|
|
585
|
+
*/
|
|
586
|
+
function setupCronLocalDiscovery(host, intervalMs) {
|
|
587
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
588
|
+
if (timer) {
|
|
589
|
+
clearInterval(timer);
|
|
590
|
+
}
|
|
591
|
+
logger_1.default.info(`Local device discovery poll started every ${intervalMs} ms`);
|
|
592
|
+
timer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
593
|
+
yield updateDeviceList(host);
|
|
594
|
+
}), intervalMs);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
581
597
|
function cleanPendingSessions(timeoutMs) {
|
|
582
598
|
return __awaiter(this, void 0, void 0, function* () {
|
|
583
599
|
const pendingSessions = yield pendingStore.getAllPendingSessions();
|
|
@@ -246,6 +246,11 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
246
246
|
else {
|
|
247
247
|
ServerManager_1.IS_HUB = true;
|
|
248
248
|
this.logger.info(`🌐 I'm a hub and I'm listening on ${pluginArgs.bindHostOrIp}:${cliArgs.port}`);
|
|
249
|
+
// Principal discovery: Background poll to prune stale/offline devices on the Hub itself
|
|
250
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const { setupCronLocalDiscovery } = yield Promise.resolve().then(() => __importStar(require('../device-utils')));
|
|
252
|
+
yield setupCronLocalDiscovery(pluginArgs.bindHostOrIp, pluginArgs.sendNodeDevicesToHubIntervalMs);
|
|
253
|
+
}))();
|
|
249
254
|
const socketServer = typedi_1.Container.get(SocketServer_1.SocketServer);
|
|
250
255
|
socketServer.initialize(httpServer);
|
|
251
256
|
const tracingService = typedi_1.Container.get(TracingService_1.TracingService);
|
package/package.json
CHANGED