devicely 2.2.12 โ 2.2.13
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/README.md +182 -81
- package/bin/devicely.js +1 -1
- package/config/devices.conf +2 -2
- package/lib/.logging-backup/aiProviders.js.backup +654 -0
- package/lib/.logging-backup/appMappings.js.backup +337 -0
- package/lib/.logging-backup/commanderService.js.backup +4427 -0
- package/lib/.logging-backup/devices.js.backup +54 -0
- package/lib/.logging-backup/doctor.js.backup +94 -0
- package/lib/.logging-backup/encryption.js.backup +61 -0
- package/lib/.logging-backup/executor.js.backup +104 -0
- package/lib/.logging-backup/hybridAI.js.backup +154 -0
- package/lib/.logging-backup/intelligentLocatorService.js.backup +1541 -0
- package/lib/.logging-backup/locatorStrategy.js.backup +342 -0
- package/lib/.logging-backup/scriptLoader.js.backup +13 -0
- package/lib/.logging-backup/server.js.backup +6298 -0
- package/lib/.logging-backup/tensorflowAI.js.backup +714 -0
- package/lib/.logging-backup/universalSessionManager.js.backup +370 -0
- package/lib/.logging-enhanced-backup/server.js.enhanced-backup +6298 -0
- package/lib/advanced-logger.js +1 -0
- package/lib/aiProviders.js +154 -15
- package/lib/aiProviders.js.strategic-backup +657 -0
- package/lib/aiProvidersConfig.js +61 -151
- package/lib/aiProvidersConfig.js.backup +218 -0
- package/lib/androidDeviceDetection.js +1 -1
- package/lib/appMappings.js +1 -1
- package/lib/commanderService.js +1 -1
- package/lib/commanderService.js.backup +5552 -0
- package/lib/deviceDetection.js +1 -1
- package/lib/devices.js +1 -1
- package/lib/devices.js.strategic-backup +57 -0
- package/lib/doctor.js +1 -1
- package/lib/encryption.js +1 -1
- package/lib/encryption.js.strategic-backup +61 -0
- package/lib/executor.js +1 -1
- package/lib/executor.js.strategic-backup +107 -0
- package/lib/frontend/asset-manifest.json +5 -3
- package/lib/frontend/index.html +1 -1
- package/lib/hybridAI.js +1 -0
- package/lib/intelligentLocatorService.js +1 -0
- package/lib/lightweightAI.js +1 -0
- package/lib/localBuiltInAI.js +1 -0
- package/lib/localBuiltInAI_backup.js +1 -0
- package/lib/localBuiltInAI_simple.js +1 -0
- package/lib/locatorStrategy.js +1 -1
- package/lib/logger-demo.js +2 -0
- package/lib/logger-integration-examples.js +102 -0
- package/lib/logger.js +1 -1
- package/lib/package.json +5 -0
- package/lib/public/asset-manifest.json +3 -3
- package/lib/public/index.html +1 -1
- package/lib/quick-start-logger.js +2 -0
- package/lib/scriptLoader.js +1 -1
- package/lib/server.js +1 -1
- package/lib/server.js.strategic-backup +6298 -0
- package/lib/tensorflowAI.js +1 -0
- package/lib/tensorflowAI.js.strategic-backup +717 -0
- package/lib/tinyAI.js +1 -0
- package/lib/universalSessionManager.js +1 -0
- package/package.json +1 -1
- package/scripts/shell/android_device_control.enc +1 -1
- package/scripts/shell/connect_ios_usb_multi_final.enc +1 -1
- package/scripts/shell/connect_ios_wireless_multi_final.enc +1 -1
- package/lib/public/index.html.bak +0 -1
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ๐ UNIVERSAL SESSION MANAGER - REVOLUTIONARY APPROACH
|
|
3
|
+
* ====================================================
|
|
4
|
+
*
|
|
5
|
+
* CORE PRINCIPLE: NEVER STORE SESSIONS, ALWAYS GET LIVE SESSIONS FROM WDA
|
|
6
|
+
*
|
|
7
|
+
* This eliminates ALL session conflicts, file corruption, and timing issues.
|
|
8
|
+
* Always returns the ACTUAL active session from the running WDA process.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const axios = require('axios');
|
|
12
|
+
const Logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
class UniversalSessionManager {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.logger = new Logger('UniversalSessionManager');
|
|
17
|
+
|
|
18
|
+
// Device port mapping (the ONLY thing we store)
|
|
19
|
+
this.devicePorts = new Map(); // Map<deviceName, port>
|
|
20
|
+
|
|
21
|
+
// Reference to connected devices (for sync with global session manager)
|
|
22
|
+
this.connectedDevices = null;
|
|
23
|
+
|
|
24
|
+
this.logger.info('๐ Universal Session Manager initialized - LIVE SESSION MODE');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ๐ฏ SET CONNECTED DEVICES REFERENCE: For port mapping sync
|
|
29
|
+
*/
|
|
30
|
+
setConnectedDevices(connectedDevices) {
|
|
31
|
+
this.connectedDevices = connectedDevices;
|
|
32
|
+
this.logger.debug(`๐ Connected devices reference set (${connectedDevices ? connectedDevices.length : 0} devices)`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ๐ฏ CORE METHOD: Get Live Active Session ID
|
|
37
|
+
* Always fetches from running WDA/UIAutomator2 process, never from storage
|
|
38
|
+
*/
|
|
39
|
+
async getLiveSessionId(deviceName, port = null) {
|
|
40
|
+
try {
|
|
41
|
+
// Get port from mapping or parameter
|
|
42
|
+
const devicePort = port || this.devicePorts.get(deviceName) || this.guessPortFromName(deviceName);
|
|
43
|
+
|
|
44
|
+
if (!devicePort) {
|
|
45
|
+
throw new Error(`No port mapping found for device: ${deviceName}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.logger.debug(`๐ Getting live session for ${deviceName}:${devicePort}...`);
|
|
49
|
+
|
|
50
|
+
// Determine if this is Android UIAutomator2 (port >= 8200) or iOS WDA (port < 8200)
|
|
51
|
+
const isUIAutomator2 = devicePort >= 8200;
|
|
52
|
+
|
|
53
|
+
if (isUIAutomator2) {
|
|
54
|
+
// Method 1A: Try UIAutomator2 /wd/hub/sessions endpoint
|
|
55
|
+
try {
|
|
56
|
+
const sessionsResponse = await axios.get(`http://localhost:${devicePort}/wd/hub/sessions`, {
|
|
57
|
+
timeout: 2000 // Reduced from 3000ms to 2000ms for faster response
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (sessionsResponse.data?.value && Array.isArray(sessionsResponse.data.value)) {
|
|
61
|
+
const activeSessions = sessionsResponse.data.value;
|
|
62
|
+
|
|
63
|
+
if (activeSessions.length > 0) {
|
|
64
|
+
const sessionId = activeSessions[0].id || activeSessions[0].sessionId;
|
|
65
|
+
this.logger.debug(`โ
Live UIAutomator2 session found via /wd/hub/sessions: ${sessionId.substring(0, 8)}... for ${deviceName}:${devicePort}`);
|
|
66
|
+
return {
|
|
67
|
+
sessionId,
|
|
68
|
+
port: devicePort,
|
|
69
|
+
platform: 'android'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (sessionsError) {
|
|
74
|
+
this.logger.debug(`UIAutomator2 /wd/hub/sessions endpoint failed for ${deviceName}:${devicePort}: ${sessionsError.message}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Method 2A: Try UIAutomator2 status endpoint
|
|
78
|
+
try {
|
|
79
|
+
const statusResponse = await axios.get(`http://localhost:${devicePort}/wd/hub/status`, {
|
|
80
|
+
timeout: 2000 // Reduced timeout
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (statusResponse.status === 200) {
|
|
84
|
+
// UIAutomator2 is running, but we need to get session from sessions endpoint
|
|
85
|
+
this.logger.debug(`UIAutomator2 server is running on ${devicePort}, but no active sessions found`);
|
|
86
|
+
}
|
|
87
|
+
} catch (statusError) {
|
|
88
|
+
this.logger.debug(`UIAutomator2 /wd/hub/status endpoint failed for ${deviceName}:${devicePort}: ${statusError.message}`);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Method 1B: Try iOS WDA /sessions endpoint (most reliable)
|
|
92
|
+
try {
|
|
93
|
+
const sessionsResponse = await axios.get(`http://localhost:${devicePort}/sessions`, {
|
|
94
|
+
timeout: 2000 // Reduced from 3000ms to 2000ms
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (sessionsResponse.data?.value && Array.isArray(sessionsResponse.data.value)) {
|
|
98
|
+
const activeSessions = sessionsResponse.data.value;
|
|
99
|
+
|
|
100
|
+
if (activeSessions.length > 0) {
|
|
101
|
+
const sessionId = activeSessions[0].id;
|
|
102
|
+
this.logger.debug(`โ
Live WDA session found via /sessions: ${sessionId.substring(0, 8)}... for ${deviceName}:${devicePort}`);
|
|
103
|
+
return sessionId;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (sessionsError) {
|
|
107
|
+
this.logger.debug(`WDA /sessions endpoint failed for ${deviceName}:${devicePort}: ${sessionsError.message}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Method 2B: Try iOS WDA status endpoint (alternative)
|
|
111
|
+
try {
|
|
112
|
+
const statusResponse = await axios.get(`http://localhost:${devicePort}/status`, {
|
|
113
|
+
timeout: 3000
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (statusResponse.data?.sessionId) {
|
|
117
|
+
const sessionId = statusResponse.data.sessionId;
|
|
118
|
+
this.logger.debug(`โ
Live WDA session found via /status: ${sessionId.substring(0, 8)}... for ${deviceName}:${devicePort}`);
|
|
119
|
+
return sessionId;
|
|
120
|
+
}
|
|
121
|
+
} catch (statusError) {
|
|
122
|
+
this.logger.debug(`WDA /status endpoint failed for ${deviceName}:${devicePort}: ${statusError.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Method 3: Check if WDA is actually running before attempting to create session
|
|
127
|
+
try {
|
|
128
|
+
// First, verify WDA is running on this port
|
|
129
|
+
const healthCheck = await axios.get(`http://localhost:${devicePort}/health`, {
|
|
130
|
+
timeout: 2000,
|
|
131
|
+
validateStatus: () => true
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (healthCheck.status !== 200) {
|
|
135
|
+
this.logger.debug(`โ ๏ธ WDA health check failed on port ${devicePort} - WDA may not be running`);
|
|
136
|
+
throw new Error(`WDA not responding on port ${devicePort}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.logger.info(`๐ No active session found, creating new session for ${deviceName}:${devicePort}...`);
|
|
140
|
+
|
|
141
|
+
const createResponse = await axios.post(`http://localhost:${devicePort}/session`, {
|
|
142
|
+
capabilities: {
|
|
143
|
+
alwaysMatch: {},
|
|
144
|
+
firstMatch: [{}]
|
|
145
|
+
}
|
|
146
|
+
}, { timeout: 10000 });
|
|
147
|
+
|
|
148
|
+
if (createResponse.data?.sessionId) {
|
|
149
|
+
const sessionId = createResponse.data.sessionId;
|
|
150
|
+
this.logger.info(`โ
Created new session: ${sessionId.substring(0, 8)}... for ${deviceName}:${devicePort}`);
|
|
151
|
+
return sessionId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Handle alternative session response format
|
|
155
|
+
if (createResponse.data?.value?.sessionId) {
|
|
156
|
+
const sessionId = createResponse.data.value.sessionId;
|
|
157
|
+
this.logger.info(`โ
Created new session (alt format): ${sessionId.substring(0, 8)}... for ${deviceName}:${devicePort}`);
|
|
158
|
+
return sessionId;
|
|
159
|
+
}
|
|
160
|
+
} catch (createError) {
|
|
161
|
+
this.logger.warn(`Failed to create session for ${deviceName}:${devicePort}: ${createError.message}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw new Error(`No active WDA session available for ${deviceName} on port ${devicePort}`);
|
|
165
|
+
|
|
166
|
+
} catch (error) {
|
|
167
|
+
this.logger.error(`โ Failed to get live session for ${deviceName}: ${error.message}`);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* ๐ฏ VALIDATE SESSION: Check if session ID is still valid
|
|
174
|
+
*/
|
|
175
|
+
async validateSession(sessionId, deviceName, port = null) {
|
|
176
|
+
try {
|
|
177
|
+
const devicePort = port || this.devicePorts.get(deviceName) || this.guessPortFromName(deviceName);
|
|
178
|
+
|
|
179
|
+
// Try multiple validation methods - start with lightest
|
|
180
|
+
|
|
181
|
+
// Method 1: Try status endpoint first (lighter)
|
|
182
|
+
try {
|
|
183
|
+
const statusResponse = await axios.get(`http://localhost:${devicePort}/status`, {
|
|
184
|
+
timeout: 1500
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (statusResponse.data && statusResponse.status === 200) {
|
|
188
|
+
this.logger.debug(`โ
Session ${sessionId.substring(0, 8)}... validated via status for ${deviceName}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
} catch (statusError) {
|
|
192
|
+
this.logger.debug(`Status validation failed for ${deviceName}: ${statusError.message}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Method 2: If status fails, try sessions endpoint
|
|
196
|
+
try {
|
|
197
|
+
const sessionsResponse = await axios.get(`http://localhost:${devicePort}/sessions`, {
|
|
198
|
+
timeout: 1500
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (sessionsResponse.data?.value && Array.isArray(sessionsResponse.data.value)) {
|
|
202
|
+
const activeSessions = sessionsResponse.data.value;
|
|
203
|
+
const sessionExists = activeSessions.find(s => s.id === sessionId);
|
|
204
|
+
|
|
205
|
+
if (sessionExists) {
|
|
206
|
+
this.logger.debug(`โ
Session ${sessionId.substring(0, 8)}... validated via sessions for ${deviceName}`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (sessionsError) {
|
|
211
|
+
this.logger.debug(`Sessions validation failed for ${deviceName}: ${sessionsError.message}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Method 3: Last resort - try source endpoint (heaviest)
|
|
215
|
+
try {
|
|
216
|
+
await axios.get(`http://localhost:${devicePort}/session/${sessionId}/source`, {
|
|
217
|
+
timeout: 2000
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
this.logger.debug(`โ
Session ${sessionId.substring(0, 8)}... validated via source for ${deviceName}`);
|
|
221
|
+
return true;
|
|
222
|
+
|
|
223
|
+
} catch (sourceError) {
|
|
224
|
+
this.logger.debug(`Source validation failed for ${deviceName}: ${sourceError.message}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return false;
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
this.logger.debug(`โ Session ${sessionId?.substring(0, 8) || 'null'}... validation failed for ${deviceName}: ${error.message}`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* ๐ฏ REGISTER DEVICE PORT: Store port mapping and sync with universal manager
|
|
237
|
+
*/
|
|
238
|
+
setDevicePort(deviceName, port) {
|
|
239
|
+
this.devicePorts.set(deviceName, port);
|
|
240
|
+
this.logger.debug(`๐ Port mapping registered: ${deviceName} โ ${port}`);
|
|
241
|
+
|
|
242
|
+
// Also register in global session manager if available
|
|
243
|
+
if (global.sessionManager) {
|
|
244
|
+
// Find device by name in connected devices to get UDID
|
|
245
|
+
let device = null;
|
|
246
|
+
|
|
247
|
+
// Try multiple ways to find the device
|
|
248
|
+
if (global.connectedDevices) {
|
|
249
|
+
device = global.connectedDevices.find(d => d.name === deviceName);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback: check if we have a connected devices reference in the session manager
|
|
253
|
+
if (!device && this.connectedDevices) {
|
|
254
|
+
device = this.connectedDevices.find(d => d.name === deviceName);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (device?.udid) {
|
|
258
|
+
try {
|
|
259
|
+
global.sessionManager.updateDevicePort(device.udid, port);
|
|
260
|
+
this.logger.debug(`๐ Synced port mapping to global session manager: ${deviceName} โ ${port}`);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
// Non-critical error
|
|
263
|
+
this.logger.debug(`โ ๏ธ Could not sync to global session manager: ${error.message}`);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
this.logger.debug(`โ ๏ธ Could not find device ${deviceName} to sync port mapping`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* ๐ฏ REMOVE DEVICE: Clean up port mapping
|
|
273
|
+
*/
|
|
274
|
+
removeDevice(deviceName) {
|
|
275
|
+
this.devicePorts.delete(deviceName);
|
|
276
|
+
this.logger.debug(`๐งน Port mapping removed for ${deviceName}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* ๐ฏ SMART PORT GUESSING: Guess port based on device name patterns
|
|
281
|
+
*/
|
|
282
|
+
guessPortFromName(deviceName) {
|
|
283
|
+
// Common port patterns based on device names
|
|
284
|
+
if (deviceName.toLowerCase().includes('ipad') && !deviceName.toLowerCase().includes('pro')) {
|
|
285
|
+
return 8101; // Regular iPads typically on 8101
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (deviceName.toLowerCase().includes('iphone')) {
|
|
289
|
+
return 8100; // iPhones typically on 8100
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check if it's likely the second device
|
|
293
|
+
if (deviceName.toLowerCase().includes('pro') || deviceName.toLowerCase().includes('suresh')) {
|
|
294
|
+
return 8101; // Second devices typically on 8101
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Default to 8100
|
|
298
|
+
return 8100;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* ๐ฏ GET SESSION WITH AUTO-RETRY: Robust session getter with options
|
|
303
|
+
*/
|
|
304
|
+
async getSessionWithRetry(deviceName, port = null, maxRetries = 2, skipValidation = false) {
|
|
305
|
+
let lastError;
|
|
306
|
+
|
|
307
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
308
|
+
try {
|
|
309
|
+
const sessionId = await this.getLiveSessionId(deviceName, port);
|
|
310
|
+
|
|
311
|
+
// Skip validation if requested (for more aggressive session usage)
|
|
312
|
+
if (skipValidation) {
|
|
313
|
+
this.logger.debug(`โ
Session acquired (validation skipped): ${sessionId.substring(0, 8)}... for ${deviceName}`);
|
|
314
|
+
return sessionId;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Validate the session works
|
|
318
|
+
const isValid = await this.validateSession(sessionId, deviceName, port);
|
|
319
|
+
|
|
320
|
+
if (isValid) {
|
|
321
|
+
return sessionId;
|
|
322
|
+
} else {
|
|
323
|
+
throw new Error('Session validation failed');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} catch (error) {
|
|
327
|
+
lastError = error;
|
|
328
|
+
|
|
329
|
+
if (attempt < maxRetries) {
|
|
330
|
+
this.logger.warn(`โ ๏ธ Session attempt ${attempt} failed for ${deviceName}, retrying... (${error.message})`);
|
|
331
|
+
|
|
332
|
+
// Wait a bit before retry
|
|
333
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
throw new Error(`Failed to get valid session for ${deviceName} after ${maxRetries} attempts: ${lastError.message}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* ๐ฏ GET ALL ACTIVE SESSIONS: For debugging/monitoring
|
|
343
|
+
*/
|
|
344
|
+
async getAllActiveSessions() {
|
|
345
|
+
const activeSessions = {};
|
|
346
|
+
|
|
347
|
+
for (const [deviceName, port] of this.devicePorts.entries()) {
|
|
348
|
+
try {
|
|
349
|
+
const sessionId = await this.getLiveSessionId(deviceName, port);
|
|
350
|
+
activeSessions[deviceName] = {
|
|
351
|
+
port,
|
|
352
|
+
sessionId,
|
|
353
|
+
status: 'active'
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
activeSessions[deviceName] = {
|
|
357
|
+
port,
|
|
358
|
+
sessionId: null,
|
|
359
|
+
status: 'inactive',
|
|
360
|
+
error: error.message
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return activeSessions;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Export singleton instance
|
|
370
|
+
module.exports = new UniversalSessionManager();
|