allow user decision when some action fails
This commit is contained in:
		| @ -1,54 +1,3 @@ | ||||
| /** | ||||
|  * Extract data from current page / multiple urls. | ||||
|  * getData(tab, itemsSelector:string, fieldSelectors:string[]) | ||||
|  * getData(tab, itemsSelector:string, fieldSelectors:string[], url:string, from:number, to:number, interval:number) | ||||
|  * getData(tab, itemsSelector:string, fieldSelectors:string, url:string, pages:number[]) | ||||
|  * getData(tab, itemsSelector:string, fieldSelectors:string[], urls:string[]) | ||||
|  * getData(tab, itemsSelector:string, fieldSelectors:string[], urls:ExtractResult) | ||||
|  * getData(itemsSelector:string, fieldSelectors:string[]) | ||||
|  * getData(itemsSelector:string, fieldSelectors:string[], url:string, from:number, to:number, interval:number) | ||||
|  * getData(itemsSelector:string, fieldSelectors:string, url:string, pages:number[]) | ||||
|  * getData(itemsSelector:string, fieldSelectors:string[], urls:string[]) | ||||
|  * getData(itemsSelector:string, fieldSelectors:string[], urls:ExtractResult) | ||||
|  * @param {...any} args | ||||
|  */ | ||||
| async function getData(...args) { | ||||
|     let tab; | ||||
|     if (typeof args[0] !== 'string') tab = args.shift(); | ||||
|     if (!testArgs(...args)) | ||||
|         throw new Error(`Invalid call arguments.\n\n${signitures}\n\n`); | ||||
|     itemsSelector = args.shift(); | ||||
|     fieldSelectors = args.shift(); | ||||
|     let urls = parseUrls(...args); | ||||
|     let data = []; | ||||
|     if (!tab) tab = await getActiveTab(true) || await getActiveTab(false); | ||||
|     if (!tab) throw new Error("Cannot find active tab."); | ||||
|     return new Promise((resolve, reject) => { | ||||
|         let pms; | ||||
|         if (urls.length) { | ||||
|             pms = urls.reduce((p, url) => p.then( | ||||
|                 results => { | ||||
|                     if (results) data.push(...results); | ||||
|                     return redirectTab(tab, url).then( | ||||
|                         () => extractTabData(tab, itemsSelector, fieldSelectors) | ||||
|                     ); | ||||
|                 }, | ||||
|                 () => p | ||||
|             ), Promise.resolve([])); | ||||
|         } else { | ||||
|             pms = extractTabData(tab, itemsSelector, fieldSelectors); | ||||
|         } | ||||
|         pms.then( | ||||
|             results => { | ||||
|                 if (results) data.push(...results); | ||||
|                 data.unshift(fieldSelectors); | ||||
|                 resolve(new ExtractResult(data)); | ||||
|             }, | ||||
|             err => reject(err) | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function parseUrls(...args) { | ||||
|     if (!args.length) return []; | ||||
|     let arg = args.shift(); | ||||
| @ -77,19 +26,26 @@ function parseUrls(...args) { | ||||
| } | ||||
|  | ||||
| function redirectTab(tab, url) { | ||||
|     let curUrl = ""; | ||||
|     return queryUrl(tab, undefined, 'Query current url...') | ||||
|         .then(u => { | ||||
|             if (url !== u) { | ||||
|                 curUrl = u; | ||||
|                 let req = { | ||||
|                     action: ACTION_GOTO_URL, | ||||
|                     url: url | ||||
|                 } | ||||
|                 return sendMessage(tab, req, `Goto url: ${url}`); | ||||
|     return queryUrl(tab).then(u => { | ||||
|         if (url !== u) { | ||||
|             let req = { | ||||
|                 action: ACTION_GOTO_URL, | ||||
|                 url: url | ||||
|             } | ||||
|         }) | ||||
|         .then(() => queryUrl(tab, url, 'Check if tab url matches expected...')) | ||||
|             let checker = async (url, err, tryCount) => { | ||||
|                 let newURL = await queryUrl(tab).catch(() => { }); | ||||
|                 if (newURL == url) return url; | ||||
|                 if ( | ||||
|                     tryCount % 5 == 0 && | ||||
|                     !confirm('Cannot navigate to target url. \nPress OK to continue, Cancel to stop.') | ||||
|                 ) { | ||||
|                     return MSG_USER_ABORT; | ||||
|                 } | ||||
|                 return undefined; | ||||
|             } | ||||
|             return sendMessage(tab, req, `Goto url: ${url}`, checker); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @ -105,8 +61,19 @@ function extractTabData(tab, itemsSelector, fieldSelectors) { | ||||
|         itemsSelector: itemsSelector, | ||||
|         fieldSelectors: fieldSelectors | ||||
|     } | ||||
|     let cond = r => !MSG_ELEMENT_NOT_FOUND.isEqual(r); | ||||
|     return sendMessage(tab, req, 'Extract data from the tab...', cond); | ||||
|     let checker = (result, err, tryCount) => { | ||||
|         if (MSG_ELEMENT_NOT_FOUND.isEqual(result)) { | ||||
|             if (tryCount % 20 == 0) { | ||||
|                 if (confirm('No data found in current page. \n\nContinue to next page?')) { | ||||
|                     return []; | ||||
|                 } | ||||
|             } else { | ||||
|                 return undefined; | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     }; | ||||
|     return sendMessage(tab, req, 'Extract data from the tab...', checker); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @ -118,23 +85,21 @@ async function ping(tab, count = 1) { | ||||
|     let req = { | ||||
|         action: ACTION_REPORT_IN | ||||
|     } | ||||
|     let cond = r => r == req.action; | ||||
|     let pong = await sendMessage(tab, req, 'Check tab availability...', cond, 1000, count).catch(() => { }); | ||||
|     let checker = r => r == req.action ? req.action : undefined; | ||||
|     let pong = await sendMessage(tab, req, 'Check tab availability...', checker, 1000, count).catch(() => { }); | ||||
|     return pong == ACTION_REPORT_IN; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * get the url of the target tab | ||||
|  * @param {any} tab target tab | ||||
|  * @param {string} expected if specified, queryUrl resolves only when tab url equals to expected | ||||
|  * @returns {Promise<string>} a promise of the url | ||||
|  */ | ||||
| function queryUrl(tab, expected, log) { | ||||
| function queryUrl(tab) { | ||||
|     let req = { | ||||
|         action: ACTION_QUERY_URL | ||||
|     } | ||||
|     let cond = url => url && (!expected || (expected && expected == url)); | ||||
|     return sendMessage(tab, req, log, cond); | ||||
|     return sendMessage(tab, req); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  | ||||
| @ -1,18 +1,21 @@ | ||||
|  | ||||
| /** | ||||
|  * Repeatedly sending a message to target tab until the response is detected good. | ||||
|  *  Sending a message to target tab repeatedly until the response is not undefined. | ||||
|  * @param {object} tab the table where to send the message | ||||
|  * @param {object} req the request data. | ||||
|  * @param {function} cond success condition function, r:any=>boolean | ||||
|  * @param {function} dataChecker (reulst:any, err:error, tryCount:number) => any.  | ||||
|  * Check and decide what value finally returns.  | ||||
|  * Return undefined to make 'sendMessage' retry.  | ||||
|  * Return MSG_USER_ABORT to cancel this promise. | ||||
|  * @param {number} interval retry interval, default: 500ms. | ||||
|  * @param {number} limit retry limit, default: 0, no limit. | ||||
|  * @param {string} log messages logged to console. | ||||
|  * @return {Promise} a promise of the response. | ||||
|  */ | ||||
| function sendMessage(tab, req, log, cond, interval, limit = 0) { | ||||
| function sendMessage(tab, req, log, dataChecker, interval, limit = 0) { | ||||
|     interval = interval || 500; | ||||
|     limit = limit && !isNaN(limit) ? limit : 0; | ||||
|     count = 0; | ||||
|     let count = 0; | ||||
|     return new Promise((resolve, reject) => { | ||||
|  | ||||
|         loop(); | ||||
| @ -30,16 +33,22 @@ function sendMessage(tab, req, log, cond, interval, limit = 0) { | ||||
|                 return; | ||||
|             } | ||||
|             count++; | ||||
|             chrome.tabs.sendMessage(tab.id, req, r => { | ||||
|             chrome.tabs.sendMessage(tab.id, req, async r => { | ||||
|                 // check error but do nothing. | ||||
|                 // do not interrupt promise chains even if error, or the task always fail when: | ||||
|                 // a tab is newly created, and the content scripts won't have time to initialize | ||||
|                 chrome.runtime.lastError; | ||||
|  | ||||
|                 let flag = !cond || cond(r); | ||||
|                 let err = chrome.runtime.lastError; | ||||
|                 let result = r; | ||||
|                 if (dataChecker) { | ||||
|                     result = await dataChecker(r, err, count); | ||||
|                     if (MSG_USER_ABORT.isEqual(result)) { | ||||
|                         reject(MSG_USER_ABORT.message); | ||||
|                     } | ||||
|                 } | ||||
|                 let flag = result !== undefined && result !== null; | ||||
|                 if (log) logger.info(log, flag ? '(OK)' : '(failed)'); | ||||
|                 if (flag) { | ||||
|                     resolve(r); | ||||
|                     resolve(result); | ||||
|                 } else { | ||||
|                     setTimeout(() => { | ||||
|                         loop(); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user