sonuprasad23 commited on
Commit
035002c
·
1 Parent(s): 0e752fc
Files changed (5) hide show
  1. .dockerignore +0 -5
  2. Dockerfile +22 -20
  3. Dockerfile.alternative +0 -54
  4. index.js +31 -25
  5. package.json +2 -3
.dockerignore CHANGED
@@ -6,9 +6,4 @@ Dockerfile*
6
  .gitignore
7
  README.md
8
  .env
9
- .nyc_output
10
- coverage
11
- .nyc_output
12
- .coverage
13
- .coverage/
14
  *.md
 
6
  .gitignore
7
  README.md
8
  .env
 
 
 
 
 
9
  *.md
Dockerfile CHANGED
@@ -1,32 +1,34 @@
1
- # Use a Node.js-ready base image with Playwright dependencies
2
- FROM mcr.microsoft.com/playwright/python:v1.22.0-focal
3
 
4
- # Set the working directory inside the container
5
  WORKDIR /usr/src/app
6
 
7
- # Install Node.js and npm (latest LTS)
 
 
 
8
  RUN apt-get update && apt-get install -y \
9
- nodejs \
10
- npm \
 
 
 
 
 
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
- # Copy package.json and package-lock.json (if available) to leverage caching
14
  COPY package*.json ./
15
 
16
- # Install application dependencies
17
- RUN npm install --omit=dev
18
-
19
- # Copy the public directory containing the UI into the container
20
- COPY public ./public
21
-
22
- # Copy the main application file
23
- COPY index.js .
24
 
25
- # Set environment to production for better performance
26
- ENV NODE_ENV=production
27
 
28
- # Expose the port the app will run on (Hugging Face Spaces uses 7860)
29
  EXPOSE 7860
30
 
31
- # The command to start the application
32
- CMD [ "npm", "start" ]
 
1
+ # Use official Node.js image
2
+ FROM node:18
3
 
4
+ # Set working directory
5
  WORKDIR /usr/src/app
6
 
7
+ # Set environment variable to skip Puppeteer chromium download
8
+ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
9
+
10
+ # Update and install dependencies for Chrome
11
  RUN apt-get update && apt-get install -y \
12
+ wget \
13
+ gnupg \
14
+ ca-certificates \
15
+ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
16
+ && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
17
+ && apt-get update \
18
+ && apt-get install -y google-chrome-stable \
19
  && rm -rf /var/lib/apt/lists/*
20
 
21
+ # Copy package files
22
  COPY package*.json ./
23
 
24
+ # Install Node.js dependencies
25
+ RUN npm install
 
 
 
 
 
 
26
 
27
+ # Copy application code
28
+ COPY . .
29
 
30
+ # Expose port
31
  EXPOSE 7860
32
 
33
+ # Start application
34
+ CMD ["npm", "start"]
Dockerfile.alternative DELETED
@@ -1,54 +0,0 @@
1
- # Alternative Dockerfile for Hugging Face Spaces
2
- # Use Ubuntu 22.04 LTS with Node.js pre-installed
3
- FROM ubuntu:22.04
4
-
5
- # Set environment variables
6
- ENV DEBIAN_FRONTEND=noninteractive
7
- ENV NODE_VERSION=20.11.1
8
- ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
9
- ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
10
-
11
- # Set the working directory
12
- WORKDIR /usr/src/app
13
-
14
- # Install system dependencies
15
- RUN apt-get update && apt-get install -y \
16
- curl \
17
- wget \
18
- gnupg \
19
- ca-certificates \
20
- apt-transport-https \
21
- software-properties-common \
22
- && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
23
- && apt-get install -y nodejs \
24
- && wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
25
- && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
26
- && apt-get update \
27
- && apt-get install -y google-chrome-stable \
28
- && apt-get clean \
29
- && rm -rf /var/lib/apt/lists/*
30
-
31
- # Copy package files
32
- COPY package*.json ./
33
-
34
- # Install Node.js dependencies
35
- RUN npm ci --only=production && npm cache clean --force
36
-
37
- # Copy application files
38
- COPY public ./public
39
- COPY index.js .
40
-
41
- # Create user for security
42
- RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
43
- && mkdir -p /home/pptruser/Downloads \
44
- && chown -R pptruser:pptruser /home/pptruser \
45
- && chown -R pptruser:pptruser /usr/src/app
46
-
47
- # Switch to non-root user
48
- USER pptruser
49
-
50
- # Expose port
51
- EXPOSE 7860
52
-
53
- # Start the application
54
- CMD ["npm", "start"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
index.js CHANGED
@@ -2,27 +2,22 @@ const express = require('express');
2
  const http = require('http');
3
  const path = require('path');
4
  const WebSocket = require('ws');
5
- const puppeteer = require('puppeteer');
6
 
7
- // --- Setup Express App & Server ---
8
  const app = express();
9
  const server = http.createServer(app);
10
  const wss = new WebSocket.Server({ server });
11
 
12
- // Serve static files from the 'public' directory
13
  app.use(express.static(path.join(__dirname, 'public')));
14
 
15
- // Health check endpoint for Hugging Face Spaces
16
  app.get('/health', (req, res) => {
17
  res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
18
  });
19
 
20
- // --- Environment Variables ---
21
  const LOGIN_EMAIL = process.env.LOGIN_EMAIL;
22
  const LOGIN_PASSWORD = process.env.LOGIN_PASSWORD;
23
  const LOGIN_URL = 'https://static.practicefusion.com/apps/auth/?sessionId=48a4227f-b3a3-49ed-bc72-6df4b0533d56&state=vn7gnwhM9JcNhBkBGJEffcseDaCaealLLV3pVktV0RA.iLHD0UfE0jM.Catalyst&clientId=l7xxb2e568db27ed45a5908aaf31f7ce9651';
24
 
25
- // --- WebSocket Logic ---
26
  function broadcast(event, data) {
27
  const message = JSON.stringify({ event, data });
28
  console.log(`Broadcasting: ${event} - ${typeof data === 'string' ? data : JSON.stringify(data)}`);
@@ -33,7 +28,6 @@ function broadcast(event, data) {
33
  });
34
  }
35
 
36
- // --- Fixed Puppeteer Bookmarklet Code ---
37
  const bookmarkletCode = `
38
  (function(){
39
  function wait(ms){return new Promise(r=>setTimeout(r,ms));}
@@ -387,8 +381,32 @@ async function runAndSuperviseAutomation() {
387
  throw new Error("LOGIN_EMAIL or LOGIN_PASSWORD secret is not set in Hugging Face Space settings.");
388
  }
389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  browser = await puppeteer.launch({
391
  headless: true,
 
392
  args: [
393
  '--no-sandbox',
394
  '--disable-setuid-sandbox',
@@ -397,16 +415,18 @@ async function runAndSuperviseAutomation() {
397
  '--disable-features=VizDisplayCompositor',
398
  '--disable-background-timer-throttling',
399
  '--disable-backgrounding-occluded-windows',
400
- '--disable-renderer-backgrounding'
 
 
 
 
401
  ]
402
  });
403
 
404
  const page = await browser.newPage();
405
 
406
- // Set user agent to avoid detection
407
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
408
 
409
- // Expose logging function to the page
410
  await page.exposeFunction('logToNode', (event, data) => {
411
  console.log(`LOG FROM BROWSER: ${event}`, data || '');
412
  broadcast(event, data);
@@ -416,7 +436,6 @@ async function runAndSuperviseAutomation() {
416
  broadcast('STATUS', 'Navigating to login page...');
417
  await page.goto(LOGIN_URL, { waitUntil: 'networkidle2', timeout: 30000 });
418
 
419
- // Wait for login form elements
420
  await page.waitForSelector('#inputUsername', { timeout: 10000 });
421
  await page.waitForSelector('#inputPswd', { timeout: 10000 });
422
  await page.waitForSelector('#loginButton', { timeout: 10000 });
@@ -424,12 +443,10 @@ async function runAndSuperviseAutomation() {
424
  console.log('Entering credentials...');
425
  broadcast('STATUS', 'Entering credentials...');
426
 
427
- // Clear and type email
428
  await page.click('#inputUsername');
429
  await page.evaluate(() => document.querySelector('#inputUsername').value = '');
430
  await page.type('#inputUsername', LOGIN_EMAIL, { delay: 100 });
431
 
432
- // Clear and type password
433
  await page.click('#inputPswd');
434
  await page.evaluate(() => document.querySelector('#inputPswd').value = '');
435
  await page.type('#inputPswd', LOGIN_PASSWORD, { delay: 100 });
@@ -437,7 +454,6 @@ async function runAndSuperviseAutomation() {
437
  console.log('Clicking login button...');
438
  broadcast('STATUS', 'Attempting to log in...');
439
 
440
- // Click login and wait for navigation
441
  await Promise.all([
442
  page.click('#loginButton'),
443
  page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 })
@@ -446,19 +462,15 @@ async function runAndSuperviseAutomation() {
446
  console.log('Login successful. Injecting automation script.');
447
  broadcast('STATUS', 'Login successful. Starting automation script...');
448
 
449
- // Inject the automation script
450
  await page.evaluate(bookmarkletCode);
451
 
452
- // Keep the browser alive and monitor
453
  console.log('Automation script running. Monitoring...');
454
  broadcast('STATUS', 'Automation is now running in the background');
455
 
456
- // Keep the process alive by checking page status periodically
457
  while (true) {
458
- await new Promise(resolve => setTimeout(resolve, 30000)); // Check every 30 seconds
459
 
460
  try {
461
- // Check if page is still alive
462
  await page.evaluate(() => document.title);
463
  } catch (error) {
464
  console.log('Page became unresponsive, restarting...');
@@ -473,19 +485,16 @@ async function runAndSuperviseAutomation() {
473
  if (browser) {
474
  await browser.close();
475
  }
476
- // If the process ever dies, wait 30 seconds and then restart it.
477
  console.log('Restarting automation in 30 seconds...');
478
  setTimeout(schedule, 30 * 1000);
479
  }
480
  }
481
 
482
- // --- Scheduler ---
483
  function schedule() {
484
  const now = new Date();
485
  const timeZone = 'America/New_York';
486
  const currentHour = parseInt(now.toLocaleString('en-US', { hour: '2-digit', hour12: false, timeZone }), 10);
487
 
488
- // Only run during business hours (8 AM - 6 PM ET)
489
  if (currentHour >= 8 && currentHour < 18) {
490
  console.log(`Starting automation at ${now.toLocaleString('en-US', { timeZone })}`);
491
  runAndSuperviseAutomation();
@@ -494,7 +503,6 @@ function schedule() {
494
  console.log(message);
495
  broadcast('STATUS', message);
496
 
497
- // Check again in 30 minutes to see if we've entered business hours
498
  setTimeout(schedule, 30 * 60 * 1000);
499
  }
500
  }
@@ -513,13 +521,11 @@ wss.on('connection', (ws) => {
513
  });
514
  });
515
 
516
- // --- Start Server ---
517
  const PORT = process.env.PORT || 7860;
518
  server.listen(PORT, '0.0.0.0', () => {
519
  console.log(`Server is running on port ${PORT}`);
520
  console.log('UI is available at the root path');
521
  console.log('Automation will start if within business hours (8 AM - 6 PM ET)');
522
 
523
- // Start the first check immediately
524
  schedule();
525
  });
 
2
  const http = require('http');
3
  const path = require('path');
4
  const WebSocket = require('ws');
5
+ const puppeteer = require('puppeteer-core');
6
 
 
7
  const app = express();
8
  const server = http.createServer(app);
9
  const wss = new WebSocket.Server({ server });
10
 
 
11
  app.use(express.static(path.join(__dirname, 'public')));
12
 
 
13
  app.get('/health', (req, res) => {
14
  res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
15
  });
16
 
 
17
  const LOGIN_EMAIL = process.env.LOGIN_EMAIL;
18
  const LOGIN_PASSWORD = process.env.LOGIN_PASSWORD;
19
  const LOGIN_URL = 'https://static.practicefusion.com/apps/auth/?sessionId=48a4227f-b3a3-49ed-bc72-6df4b0533d56&state=vn7gnwhM9JcNhBkBGJEffcseDaCaealLLV3pVktV0RA.iLHD0UfE0jM.Catalyst&clientId=l7xxb2e568db27ed45a5908aaf31f7ce9651';
20
 
 
21
  function broadcast(event, data) {
22
  const message = JSON.stringify({ event, data });
23
  console.log(`Broadcasting: ${event} - ${typeof data === 'string' ? data : JSON.stringify(data)}`);
 
28
  });
29
  }
30
 
 
31
  const bookmarkletCode = `
32
  (function(){
33
  function wait(ms){return new Promise(r=>setTimeout(r,ms));}
 
381
  throw new Error("LOGIN_EMAIL or LOGIN_PASSWORD secret is not set in Hugging Face Space settings.");
382
  }
383
 
384
+ const possiblePaths = [
385
+ '/usr/bin/google-chrome-stable',
386
+ '/usr/bin/google-chrome',
387
+ '/usr/bin/chromium-browser',
388
+ '/usr/bin/chromium'
389
+ ];
390
+
391
+ let executablePath;
392
+ for (const path of possiblePaths) {
393
+ try {
394
+ require('fs').accessSync(path);
395
+ executablePath = path;
396
+ console.log(`Found Chrome at: ${path}`);
397
+ break;
398
+ } catch (err) {
399
+ continue;
400
+ }
401
+ }
402
+
403
+ if (!executablePath) {
404
+ throw new Error('Chrome executable not found. Please ensure Chrome is installed.');
405
+ }
406
+
407
  browser = await puppeteer.launch({
408
  headless: true,
409
+ executablePath: executablePath,
410
  args: [
411
  '--no-sandbox',
412
  '--disable-setuid-sandbox',
 
415
  '--disable-features=VizDisplayCompositor',
416
  '--disable-background-timer-throttling',
417
  '--disable-backgrounding-occluded-windows',
418
+ '--disable-renderer-backgrounding',
419
+ '--disable-gpu',
420
+ '--disable-extensions',
421
+ '--no-first-run',
422
+ '--no-default-browser-check'
423
  ]
424
  });
425
 
426
  const page = await browser.newPage();
427
 
 
428
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
429
 
 
430
  await page.exposeFunction('logToNode', (event, data) => {
431
  console.log(`LOG FROM BROWSER: ${event}`, data || '');
432
  broadcast(event, data);
 
436
  broadcast('STATUS', 'Navigating to login page...');
437
  await page.goto(LOGIN_URL, { waitUntil: 'networkidle2', timeout: 30000 });
438
 
 
439
  await page.waitForSelector('#inputUsername', { timeout: 10000 });
440
  await page.waitForSelector('#inputPswd', { timeout: 10000 });
441
  await page.waitForSelector('#loginButton', { timeout: 10000 });
 
443
  console.log('Entering credentials...');
444
  broadcast('STATUS', 'Entering credentials...');
445
 
 
446
  await page.click('#inputUsername');
447
  await page.evaluate(() => document.querySelector('#inputUsername').value = '');
448
  await page.type('#inputUsername', LOGIN_EMAIL, { delay: 100 });
449
 
 
450
  await page.click('#inputPswd');
451
  await page.evaluate(() => document.querySelector('#inputPswd').value = '');
452
  await page.type('#inputPswd', LOGIN_PASSWORD, { delay: 100 });
 
454
  console.log('Clicking login button...');
455
  broadcast('STATUS', 'Attempting to log in...');
456
 
 
457
  await Promise.all([
458
  page.click('#loginButton'),
459
  page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 })
 
462
  console.log('Login successful. Injecting automation script.');
463
  broadcast('STATUS', 'Login successful. Starting automation script...');
464
 
 
465
  await page.evaluate(bookmarkletCode);
466
 
 
467
  console.log('Automation script running. Monitoring...');
468
  broadcast('STATUS', 'Automation is now running in the background');
469
 
 
470
  while (true) {
471
+ await new Promise(resolve => setTimeout(resolve, 30000));
472
 
473
  try {
 
474
  await page.evaluate(() => document.title);
475
  } catch (error) {
476
  console.log('Page became unresponsive, restarting...');
 
485
  if (browser) {
486
  await browser.close();
487
  }
 
488
  console.log('Restarting automation in 30 seconds...');
489
  setTimeout(schedule, 30 * 1000);
490
  }
491
  }
492
 
 
493
  function schedule() {
494
  const now = new Date();
495
  const timeZone = 'America/New_York';
496
  const currentHour = parseInt(now.toLocaleString('en-US', { hour: '2-digit', hour12: false, timeZone }), 10);
497
 
 
498
  if (currentHour >= 8 && currentHour < 18) {
499
  console.log(`Starting automation at ${now.toLocaleString('en-US', { timeZone })}`);
500
  runAndSuperviseAutomation();
 
503
  console.log(message);
504
  broadcast('STATUS', message);
505
 
 
506
  setTimeout(schedule, 30 * 60 * 1000);
507
  }
508
  }
 
521
  });
522
  });
523
 
 
524
  const PORT = process.env.PORT || 7860;
525
  server.listen(PORT, '0.0.0.0', () => {
526
  console.log(`Server is running on port ${PORT}`);
527
  console.log('UI is available at the root path');
528
  console.log('Automation will start if within business hours (8 AM - 6 PM ET)');
529
 
 
530
  schedule();
531
  });
package.json CHANGED
@@ -4,12 +4,11 @@
4
  "description": "Automates a task on Practice Fusion with a web UI for Hugging Face Spaces.",
5
  "main": "index.js",
6
  "scripts": {
7
- "start": "node index.js",
8
- "dev": "node index.js"
9
  },
10
  "dependencies": {
11
  "express": "^4.19.2",
12
- "puppeteer": "^22.6.5",
13
  "ws": "^8.17.0"
14
  },
15
  "engines": {
 
4
  "description": "Automates a task on Practice Fusion with a web UI for Hugging Face Spaces.",
5
  "main": "index.js",
6
  "scripts": {
7
+ "start": "node index.js"
 
8
  },
9
  "dependencies": {
10
  "express": "^4.19.2",
11
+ "puppeteer-core": "^21.0.0",
12
  "ws": "^8.17.0"
13
  },
14
  "engines": {