From 8c2ac3b6d0be9d866d9794be88a92c108526a4b9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 21 Feb 2023 21:21:55 +0100 Subject: [PATCH 1/2] add LOGIN_TIMEOUT (180s) for PW, but prompts still wait forever --- README.md | 1 + config.js | 1 + epic-games.js | 8 ++++---- gog.js | 7 ++++--- notify-test.js | 4 +--- prime-gaming.js | 7 ++++--- util.js | 1 + 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7c36c0b..3608b52 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Available options/variables and their default values: | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | +| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | diff --git a/config.js b/config.js index e7408d4..bdee57e 100644 --- a/config.js +++ b/config.js @@ -12,6 +12,7 @@ export const cfg = { width: Number(process.env.WIDTH) || 1280, // width of the opened browser height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s + login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher 3min timeout for login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization diff --git a/epic-games.js b/epic-games.js index dff1344..e4f5293 100644 --- a/epic-games.js +++ b/epic-games.js @@ -56,11 +56,11 @@ try { while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); - context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); - if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser.'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.eg_email || await prompt({message: 'Enter email'}); const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -85,7 +85,7 @@ try { notify('epic-games: no longer signed in and not enough options set for automatic login.'); } await page.waitForURL(URL_CLAIM); - context.setDefaultTimeout(cfg.timeout); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); diff --git a/gog.js b/gog.js index 568fe80..fade890 100644 --- a/gog.js +++ b/gog.js @@ -40,9 +40,10 @@ try { // it then creates an iframe for the login await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); - context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); if (cfg.gog_email && cfg.gog_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.gog_email || await prompt({message: 'Enter email'}); const password = email && (cfg.gog_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -76,7 +77,7 @@ try { process.exit(1); } } - context.setDefaultTimeout(cfg.timeout); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! console.log(`Signed in as '${user}'`); diff --git a/notify-test.js b/notify-test.js index af883df..d8f4dab 100644 --- a/notify-test.js +++ b/notify-test.js @@ -1,6 +1,4 @@ -import { html_game_list, notify } from "./util.js"; - -const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); +import { delay, html_game_list, notify } from "./util.js"; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL diff --git a/prime-gaming.js b/prime-gaming.js index 5a3278c..8988557 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -37,9 +37,10 @@ try { while (await page.locator('button:has-text("Sign in")').count() > 0) { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); - if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); if (cfg.pg_email && cfg.pg_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.pg_email || await prompt({message: 'Enter email'}); const password = email && (cfg.pg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -67,7 +68,7 @@ try { console.log('Waiting for you to login in the browser.'); notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { - console.log('Please run `SHOW=1 node prime-gaming` to login in the opened browser.'); + console.log('Run `SHOW=1 node prime-gaming` to login in the opened browser.'); await context.close(); // finishes potential recording process.exit(1); } diff --git a/util.js b/util.js index d64b94b..c7c3bc9 100644 --- a/util.js +++ b/util.js @@ -17,6 +17,7 @@ export const jsonDb = async file => { }; +export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); // same as datetime() but for local timezone, e.g., UTC + 2h for the above in DE From efeccf949324b8e585aea9fac7a5bfe5ae0cdeb6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 21 Feb 2023 23:25:18 +0100 Subject: [PATCH 2/2] use enquirer instead of prompts, use plugin for cancel after timeout --- README.md | 2 +- config.js | 2 +- package-lock.json | 78 ++++++++++++++++++++--------------------------- package.json | 2 +- util.js | 19 ++++++++---- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 3608b52..a76ce74 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Available options/variables and their default values: | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | -| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. | +| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. Will wait twice (prompt + manual login). | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | diff --git a/config.js b/config.js index bdee57e..bdb6bd9 100644 --- a/config.js +++ b/config.js @@ -12,7 +12,7 @@ export const cfg = { width: Number(process.env.WIDTH) || 1280, // width of the opened browser height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s - login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher 3min timeout for login + login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher timeout for login, will wait twice: prompt + wait for manual login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization diff --git a/package-lock.json b/package-lock.json index 1318365..18c47bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", + "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", "playwright": "^1.30.0", - "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, @@ -73,6 +73,14 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -177,6 +185,17 @@ "node": ">=12" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -311,14 +330,6 @@ "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -450,18 +461,6 @@ "node": ">=14" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -630,11 +629,6 @@ "node": ">=8" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, "node_modules/steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", @@ -738,6 +732,11 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" + }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -810,6 +809,14 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -915,11 +922,6 @@ "is-buffer": "^1.1.5" } }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -1013,15 +1015,6 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==" }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, "puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -1111,11 +1104,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, "steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", diff --git a/package.json b/package.json index cfd941c..38b31a1 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", + "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", "playwright": "^1.30.0", - "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "repository": { diff --git a/util.js b/util.js index c7c3bc9..cd05832 100644 --- a/util.js +++ b/util.js @@ -77,12 +77,19 @@ export const stealth = async (context) => { } }; - -import prompts from 'prompts'; // alternatives: enquirer, inquirer -// import enquirer from 'enquirer'; const { prompt } = enquirer; -// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? -export const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; - +// used prompts before, but couldn't cancel prompt +// alternative inquirer is big (node_modules 29MB, enquirer 9.7MB, prompts 9.8MB, none 9.4MB) and slower +import Enquirer from 'enquirer'; const enquirer = new Enquirer(); +const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms + enquirer.on('prompt', prompt => { + const t = setTimeout(() => { prompt.hint = () => 'timeout'; prompt.cancel(); }, timeout); + prompt.on('submit', _ => clearTimeout(t)); + prompt.on('cancel', _ => clearTimeout(t)); + }); +} +enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option +// single prompt that just returns the non-empty value instead of an object +export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message: 'Enter value', ...o}).then(r => r.name).catch(_ => {}); // notifications via apprise CLI import { exec } from 'child_process';