parse-dashboard-analytics 7.4.2 → 7.4.5

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 (43) hide show
  1. package/Parse-Dashboard/Authentication.js +9 -4
  2. package/Parse-Dashboard/CLI/mfa.js +11 -5
  3. package/Parse-Dashboard/app.js +93 -26
  4. package/Parse-Dashboard/browser-control/BrowserControlAPI.js +468 -0
  5. package/Parse-Dashboard/browser-control/BrowserEventStream.js +294 -0
  6. package/Parse-Dashboard/browser-control/BrowserSessionManager.js +304 -0
  7. package/Parse-Dashboard/browser-control/README.md +852 -0
  8. package/Parse-Dashboard/browser-control/ServerOrchestrator.js +334 -0
  9. package/Parse-Dashboard/browser-control/index.js +27 -0
  10. package/Parse-Dashboard/browser-control/setup.js +405 -0
  11. package/Parse-Dashboard/public/bundles/120.bundle.js +1 -1
  12. package/Parse-Dashboard/public/bundles/183.bundle.js +1 -1
  13. package/Parse-Dashboard/public/bundles/19.bundle.js +1 -1
  14. package/Parse-Dashboard/public/bundles/221.bundle.js +1 -1
  15. package/Parse-Dashboard/public/bundles/372.bundle.js +1 -1
  16. package/Parse-Dashboard/public/bundles/448.bundle.js +1 -1
  17. package/Parse-Dashboard/public/bundles/573.bundle.js +1 -1
  18. package/Parse-Dashboard/public/bundles/578.bundle.js +1 -1
  19. package/Parse-Dashboard/public/bundles/584.bundle.js +1 -1
  20. package/Parse-Dashboard/public/bundles/629.bundle.js +1 -1
  21. package/Parse-Dashboard/public/bundles/643.bundle.js +1 -1
  22. package/Parse-Dashboard/public/bundles/647.bundle.js +1 -1
  23. package/Parse-Dashboard/public/bundles/701.bundle.js +1 -1
  24. package/Parse-Dashboard/public/bundles/729.bundle.js +1 -1
  25. package/Parse-Dashboard/public/bundles/75.bundle.js +1 -1
  26. package/Parse-Dashboard/public/bundles/753.bundle.js +1 -1
  27. package/Parse-Dashboard/public/bundles/817.bundle.js +1 -1
  28. package/Parse-Dashboard/public/bundles/844.bundle.js +1 -1
  29. package/Parse-Dashboard/public/bundles/862.bundle.js +1 -1
  30. package/Parse-Dashboard/public/bundles/866.bundle.js +1 -1
  31. package/Parse-Dashboard/public/bundles/874.bundle.js +1 -1
  32. package/Parse-Dashboard/public/bundles/881.bundle.js +1 -1
  33. package/Parse-Dashboard/public/bundles/929.bundle.js +1 -1
  34. package/Parse-Dashboard/public/bundles/950.bundle.js +1 -1
  35. package/Parse-Dashboard/public/bundles/97.bundle.js +1 -1
  36. package/Parse-Dashboard/public/bundles/976.bundle.js +1 -1
  37. package/Parse-Dashboard/public/bundles/dashboard.bundle.js +1 -1
  38. package/Parse-Dashboard/public/bundles/dashboard.bundle.js.LICENSE.txt +27 -33
  39. package/Parse-Dashboard/public/bundles/login.bundle.js +1 -1
  40. package/Parse-Dashboard/public/bundles/sprites.svg +294 -154
  41. package/Parse-Dashboard/server.js +33 -2
  42. package/README.md +475 -117
  43. package/package.json +68 -53
@@ -0,0 +1,334 @@
1
+ const { spawn } = require('child_process');
2
+ const express = require('express');
3
+
4
+ // ESM-only modules - use .default for CommonJS compatibility
5
+ const getPortModule = require('get-port');
6
+ const getPort = getPortModule.default || getPortModule;
7
+ const fetchModule = require('node-fetch');
8
+ const fetch = fetchModule.default || fetchModule;
9
+
10
+ /**
11
+ * ServerOrchestrator
12
+ *
13
+ * Manages Parse Server and Parse Dashboard instances for browser testing.
14
+ * Handles starting servers on dynamic ports, health checks, and cleanup.
15
+ *
16
+ * Features:
17
+ * - Programmatically start Parse Server via child process
18
+ * - Start Parse Dashboard on available port
19
+ * - Health check utilities to wait for server readiness
20
+ * - Clean shutdown and resource cleanup
21
+ */
22
+ class ServerOrchestrator {
23
+ constructor() {
24
+ this.parseServerProcess = null;
25
+ this.dashboardServer = null;
26
+ this.parseServerConfig = null;
27
+ this.dashboardConfig = null;
28
+ }
29
+
30
+ /**
31
+ * Start Parse Server programmatically
32
+ * @param {Object} options - Configuration options
33
+ * @param {number} options.port - Port for Parse Server (default: find available)
34
+ * @param {string} options.appId - Application ID (default: 'testAppId')
35
+ * @param {string} options.masterKey - Master key (default: 'testMasterKey')
36
+ * @param {string} options.databaseURI - Database URI (default: 'mongodb://localhost:27017/test')
37
+ * @param {string} options.mountPath - Mount path (default: '/parse')
38
+ * @returns {Promise<Object>} - { process, port, serverURL, appId, masterKey }
39
+ */
40
+ async startParseServer(options = {}) {
41
+ // Find available port
42
+ const port = options.port || await getPort({ port: 1337 });
43
+ const appId = options.appId || 'testAppId';
44
+ const masterKey = options.masterKey || 'testMasterKey';
45
+ const databaseURI = options.databaseURI || 'mongodb://localhost:27017/test';
46
+ const mountPath = options.mountPath || '/parse';
47
+ const serverURL = `http://localhost:${port}${mountPath}`;
48
+
49
+ // Check if parse-server is available
50
+ try {
51
+ require.resolve('parse-server');
52
+ } catch {
53
+ throw new Error(
54
+ 'parse-server is not installed. Please install it with: npm install parse-server --save-dev'
55
+ );
56
+ }
57
+
58
+ return new Promise((resolve, reject) => {
59
+ // Spawn parse-server process
60
+ const parseServer = spawn('npx', [
61
+ 'parse-server',
62
+ '--appId', appId,
63
+ '--masterKey', masterKey,
64
+ '--databaseURI', databaseURI,
65
+ '--port', port.toString(),
66
+ '--mountPath', mountPath,
67
+ '--serverURL', serverURL
68
+ ], {
69
+ stdio: 'pipe',
70
+ env: { ...process.env }
71
+ });
72
+
73
+ let serverStarted = false;
74
+ let startupOutput = '';
75
+
76
+ // Collect stdout for diagnostics (but don't use for startup detection)
77
+ parseServer.stdout.on('data', (data) => {
78
+ startupOutput += data.toString();
79
+ });
80
+
81
+ parseServer.stderr.on('data', (data) => {
82
+ console.error('[Parse Server Error]:', data.toString());
83
+ });
84
+
85
+ parseServer.on('error', (error) => {
86
+ if (!serverStarted) {
87
+ reject(new Error(`Failed to start Parse Server: ${error.message}`));
88
+ }
89
+ });
90
+
91
+ parseServer.on('exit', (code) => {
92
+ if (!serverStarted && code !== 0) {
93
+ reject(new Error(`Parse Server exited with code ${code}. Output: ${startupOutput}`));
94
+ }
95
+ });
96
+
97
+ // Build health check URL from configured mount path
98
+ // Normalize mount path: ensure single leading slash, no trailing slash
99
+ const normalizedMountPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
100
+ const healthPath = normalizedMountPath.endsWith('/')
101
+ ? `${normalizedMountPath}health`
102
+ : `${normalizedMountPath}/health`;
103
+ const healthUrl = `http://localhost:${port}${healthPath}`;
104
+
105
+ // Poll health endpoint to detect when server is ready (more reliable than string matching)
106
+ this.waitForServer(healthUrl, 20000)
107
+ .then(() => {
108
+ serverStarted = true;
109
+ this.parseServerProcess = parseServer;
110
+ this.parseServerConfig = {
111
+ process: parseServer,
112
+ port,
113
+ serverURL,
114
+ appId,
115
+ masterKey,
116
+ databaseURI,
117
+ mountPath
118
+ };
119
+ resolve(this.parseServerConfig);
120
+ })
121
+ .catch(err => {
122
+ if (!serverStarted) {
123
+ parseServer.kill();
124
+ reject(new Error(`Parse Server health check failed: ${err.message}. Output: ${startupOutput}`));
125
+ }
126
+ });
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Start Parse Dashboard
132
+ * @param {Object} parseServerConfig - Parse Server configuration from startParseServer()
133
+ * @param {Object} options - Dashboard options
134
+ * @param {number} options.port - Port for Dashboard (default: find available)
135
+ * @param {string} options.appName - App name (default: 'TestApp')
136
+ * @param {string} options.mount - Mount path (default: '/')
137
+ * @returns {Promise<Object>} - { server, port, url }
138
+ */
139
+ async startDashboard(parseServerConfig, options = {}) {
140
+ if (!parseServerConfig) {
141
+ throw new Error('parseServerConfig is required');
142
+ }
143
+
144
+ // Find available port
145
+ const port = options.port || await getPort({ port: 4040 });
146
+ const appName = options.appName || 'TestApp';
147
+ const mount = options.mount || '/';
148
+
149
+ try {
150
+ // Import Parse Dashboard app
151
+ const ParseDashboard = require('../app');
152
+
153
+ // Dashboard settings
154
+ const dashboardSettings = {
155
+ apps: [{
156
+ serverURL: parseServerConfig.serverURL,
157
+ appId: parseServerConfig.appId,
158
+ masterKey: parseServerConfig.masterKey,
159
+ appName: appName
160
+ }],
161
+ trustProxy: 1
162
+ };
163
+
164
+ // Create Express app
165
+ const app = express();
166
+
167
+ // Mount Parse Dashboard
168
+ app.use(mount, ParseDashboard(dashboardSettings, { allowInsecureHTTP: true }));
169
+
170
+ // Start server
171
+ return new Promise((resolve, reject) => {
172
+ const server = app.listen(port, () => {
173
+ const url = `http://localhost:${port}${mount}`;
174
+ this.dashboardServer = server;
175
+ this.dashboardConfig = {
176
+ server,
177
+ port,
178
+ url,
179
+ mount,
180
+ appName,
181
+ settings: dashboardSettings
182
+ };
183
+
184
+ console.log(`Parse Dashboard started at ${url}`);
185
+ resolve(this.dashboardConfig);
186
+ });
187
+
188
+ // Handle errors via server's 'error' event (correct approach)
189
+ server.on('error', (err) => {
190
+ reject(new Error(`Failed to start Dashboard: ${err.message}`));
191
+ });
192
+ });
193
+ } catch (error) {
194
+ throw new Error(`Failed to start Dashboard: ${error.message}`);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Wait for a server to respond to health check
200
+ * @param {string} healthUrl - Health check URL
201
+ * @param {number} timeout - Timeout in milliseconds (default: 10000)
202
+ * @returns {Promise<void>}
203
+ * @private
204
+ */
205
+ async waitForServer(healthUrl, timeout = 10000) {
206
+ const startTime = Date.now();
207
+ const interval = 500; // Check every 500ms
208
+
209
+ while (Date.now() - startTime < timeout) {
210
+ try {
211
+ const response = await fetch(healthUrl);
212
+ if (response.ok || response.status === 200 || response.status === 404) {
213
+ return; // Server is responding
214
+ }
215
+ } catch {
216
+ // Server not ready yet, continue waiting
217
+ }
218
+
219
+ // Wait before next attempt
220
+ await new Promise(resolve => setTimeout(resolve, interval));
221
+ }
222
+
223
+ throw new Error(`Server health check timeout after ${timeout}ms`);
224
+ }
225
+
226
+ /**
227
+ * Stop Parse Server
228
+ * @returns {Promise<void>}
229
+ */
230
+ async stopParseServer() {
231
+ if (!this.parseServerProcess) {
232
+ return;
233
+ }
234
+
235
+ // Capture reference to avoid race conditions
236
+ const proc = this.parseServerProcess;
237
+
238
+ // Check if process already exited
239
+ if (proc.exitCode !== null || proc.killed) {
240
+ this.parseServerProcess = null;
241
+ this.parseServerConfig = null;
242
+ console.log('Parse Server already stopped');
243
+ return;
244
+ }
245
+
246
+ return new Promise((resolve) => {
247
+ let forceKillTimeout = null;
248
+
249
+ // Attach exit handler BEFORE sending signals
250
+ proc.once('exit', () => {
251
+ // Clear the force kill timeout
252
+ if (forceKillTimeout) {
253
+ clearTimeout(forceKillTimeout);
254
+ }
255
+
256
+ // Null references exactly once
257
+ this.parseServerProcess = null;
258
+ this.parseServerConfig = null;
259
+ console.log('Parse Server stopped');
260
+ resolve();
261
+ });
262
+
263
+ // Try graceful shutdown first
264
+ proc.kill('SIGTERM');
265
+
266
+ // Force kill after 5 seconds if still running
267
+ forceKillTimeout = setTimeout(() => {
268
+ // Use captured reference, not this.parseServerProcess
269
+ if (!proc.killed && proc.exitCode === null) {
270
+ proc.kill('SIGKILL');
271
+ }
272
+ }, 5000);
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Stop Parse Dashboard
278
+ * @returns {Promise<void>}
279
+ */
280
+ async stopDashboard() {
281
+ if (!this.dashboardServer) {
282
+ return;
283
+ }
284
+
285
+ // Capture reference to avoid race conditions
286
+ const server = this.dashboardServer;
287
+
288
+ return new Promise((resolve) => {
289
+ server.close(() => {
290
+ this.dashboardServer = null;
291
+ this.dashboardConfig = null;
292
+ console.log('Parse Dashboard stopped');
293
+ resolve();
294
+ });
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Stop all servers
300
+ * @returns {Promise<void>}
301
+ */
302
+ async stopAll() {
303
+ await Promise.all([
304
+ this.stopDashboard(),
305
+ this.stopParseServer()
306
+ ]);
307
+ }
308
+
309
+ /**
310
+ * Get current server status
311
+ * @returns {Object} - Status information
312
+ */
313
+ getStatus() {
314
+ return {
315
+ parseServer: this.parseServerConfig ? {
316
+ running: true,
317
+ port: this.parseServerConfig.port,
318
+ serverURL: this.parseServerConfig.serverURL,
319
+ appId: this.parseServerConfig.appId
320
+ } : {
321
+ running: false
322
+ },
323
+ dashboard: this.dashboardConfig ? {
324
+ running: true,
325
+ port: this.dashboardConfig.port,
326
+ url: this.dashboardConfig.url
327
+ } : {
328
+ running: false
329
+ }
330
+ };
331
+ }
332
+ }
333
+
334
+ module.exports = ServerOrchestrator;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Browser Control for Parse Dashboard
3
+ *
4
+ * Real-time browser automation system for AI agents to verify features during development.
5
+ * This is a development-time debugging tool, NOT a replacement for unit/E2E tests.
6
+ *
7
+ * Usage:
8
+ * npm run browser-control
9
+ *
10
+ * API:
11
+ * HTTP: http://localhost:4040/browser-control/
12
+ * WebSocket: ws://localhost:4040/browser-control/stream/:sessionId
13
+ *
14
+ * @module browser-control
15
+ */
16
+
17
+ const BrowserSessionManager = require('./BrowserSessionManager');
18
+ const BrowserControlAPI = require('./BrowserControlAPI');
19
+ const BrowserEventStream = require('./BrowserEventStream');
20
+ const ServerOrchestrator = require('./ServerOrchestrator');
21
+
22
+ module.exports = {
23
+ BrowserSessionManager,
24
+ BrowserControlAPI,
25
+ BrowserEventStream,
26
+ ServerOrchestrator
27
+ };