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) { | function parseUrls(...args) { | ||||||
|     if (!args.length) return []; |     if (!args.length) return []; | ||||||
|     let arg = args.shift(); |     let arg = args.shift(); | ||||||
| @ -77,19 +26,26 @@ function parseUrls(...args) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function redirectTab(tab, url) { | function redirectTab(tab, url) { | ||||||
|     let curUrl = ""; |     return queryUrl(tab).then(u => { | ||||||
|     return queryUrl(tab, undefined, 'Query current url...') |  | ||||||
|         .then(u => { |  | ||||||
|         if (url !== u) { |         if (url !== u) { | ||||||
|                 curUrl = u; |  | ||||||
|             let req = { |             let req = { | ||||||
|                 action: ACTION_GOTO_URL, |                 action: ACTION_GOTO_URL, | ||||||
|                 url: url |                 url: url | ||||||
|             } |             } | ||||||
|                 return sendMessage(tab, req, `Goto url: ${url}`); |             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; | ||||||
|         .then(() => queryUrl(tab, url, 'Check if tab url matches expected...')) |             } | ||||||
|  |             return sendMessage(tab, req, `Goto url: ${url}`, checker); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -105,8 +61,19 @@ function extractTabData(tab, itemsSelector, fieldSelectors) { | |||||||
|         itemsSelector: itemsSelector, |         itemsSelector: itemsSelector, | ||||||
|         fieldSelectors: fieldSelectors |         fieldSelectors: fieldSelectors | ||||||
|     } |     } | ||||||
|     let cond = r => !MSG_ELEMENT_NOT_FOUND.isEqual(r); |     let checker = (result, err, tryCount) => { | ||||||
|     return sendMessage(tab, req, 'Extract data from the tab...', cond); |         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 = { |     let req = { | ||||||
|         action: ACTION_REPORT_IN |         action: ACTION_REPORT_IN | ||||||
|     } |     } | ||||||
|     let cond = r => r == req.action; |     let checker = r => r == req.action ? req.action : undefined; | ||||||
|     let pong = await sendMessage(tab, req, 'Check tab availability...', cond, 1000, count).catch(() => { }); |     let pong = await sendMessage(tab, req, 'Check tab availability...', checker, 1000, count).catch(() => { }); | ||||||
|     return pong == ACTION_REPORT_IN; |     return pong == ACTION_REPORT_IN; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * get the url of the target tab |  * get the url of the target tab | ||||||
|  * @param {any} tab 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 |  * @returns {Promise<string>} a promise of the url | ||||||
|  */ |  */ | ||||||
| function queryUrl(tab, expected, log) { | function queryUrl(tab) { | ||||||
|     let req = { |     let req = { | ||||||
|         action: ACTION_QUERY_URL |         action: ACTION_QUERY_URL | ||||||
|     } |     } | ||||||
|     let cond = url => url && (!expected || (expected && expected == url)); |     return sendMessage(tab, req); | ||||||
|     return sendMessage(tab, req, log, cond); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -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} tab the table where to send the message | ||||||
|  * @param {object} req the request data. |  * @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} interval retry interval, default: 500ms. | ||||||
|  * @param {number} limit retry limit, default: 0, no limit. |  * @param {number} limit retry limit, default: 0, no limit. | ||||||
|  * @param {string} log messages logged to console. |  * @param {string} log messages logged to console. | ||||||
|  * @return {Promise} a promise of the response. |  * @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; |     interval = interval || 500; | ||||||
|     limit = limit && !isNaN(limit) ? limit : 0; |     limit = limit && !isNaN(limit) ? limit : 0; | ||||||
|     count = 0; |     let count = 0; | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|  |  | ||||||
|         loop(); |         loop(); | ||||||
| @ -30,16 +33,22 @@ function sendMessage(tab, req, log, cond, interval, limit = 0) { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             count++; |             count++; | ||||||
|             chrome.tabs.sendMessage(tab.id, req, r => { |             chrome.tabs.sendMessage(tab.id, req, async r => { | ||||||
|                 // check error but do nothing. |                 // check error but do nothing. | ||||||
|                 // do not interrupt promise chains even if error, or the task always fail when: |                 // 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 |                 // a tab is newly created, and the content scripts won't have time to initialize | ||||||
|                 chrome.runtime.lastError; |                 let err = chrome.runtime.lastError; | ||||||
|  |                 let result = r; | ||||||
|                 let flag = !cond || cond(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 (log) logger.info(log, flag ? '(OK)' : '(failed)'); | ||||||
|                 if (flag) { |                 if (flag) { | ||||||
|                     resolve(r); |                     resolve(result); | ||||||
|                 } else { |                 } else { | ||||||
|                     setTimeout(() => { |                     setTimeout(() => { | ||||||
|                         loop(); |                         loop(); | ||||||
|  | |||||||
| @ -1,7 +1,12 @@ | |||||||
| (function () { | (function () { | ||||||
|  |     let asleep = false; | ||||||
|     chrome.runtime.onMessage.addListener( |     chrome.runtime.onMessage.addListener( | ||||||
|         function (request, sender, sendResponse) { |         function (request, sender, sendResponse) { | ||||||
|             if (!request.action) return; |             if (!request.action) return; | ||||||
|  |             if (asleep && ACTION_WAKEUP != request.action) { | ||||||
|  |                 sendResponse && sendResponse(undefined); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|             // console.log("Recieved request:",request); |             // console.log("Recieved request:",request); | ||||||
|             doAction(request, sender).then(r => sendResponse && sendResponse(r)); |             doAction(request, sender).then(r => sendResponse && sendResponse(r)); | ||||||
|             // return true to indicate you wish to send a response asynchronously |             // return true to indicate you wish to send a response asynchronously | ||||||
| @ -16,6 +21,8 @@ | |||||||
|                 return data; |                 return data; | ||||||
|             case ACTION_GOTO_URL: |             case ACTION_GOTO_URL: | ||||||
|                 window.location.replace(request.url); |                 window.location.replace(request.url); | ||||||
|  |                 // should not recieve any request until the page & script reload | ||||||
|  |                 asleep = true; | ||||||
|                 return request.url; |                 return request.url; | ||||||
|             case ACTION_REPORT_IN: |             case ACTION_REPORT_IN: | ||||||
|                 return request.action; |                 return request.action; | ||||||
| @ -29,6 +36,12 @@ | |||||||
|                     1000, |                     1000, | ||||||
|                     10 |                     10 | ||||||
|                 ) |                 ) | ||||||
|  |             case ACTION_SLEEP: | ||||||
|  |                 asleep = true; | ||||||
|  |                 return "Content script is sleeping."; | ||||||
|  |             case ACTION_WAKEUP: | ||||||
|  |                 asleep = false; | ||||||
|  |                 return "Content script is available."; | ||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -6,3 +6,5 @@ const ACTION_REPORT_IN = `${EXT_NAME}:ReportIn`; | |||||||
| const ACTION_QUERY_URL = `${EXT_NAME}:QueryURL`; | const ACTION_QUERY_URL = `${EXT_NAME}:QueryURL`; | ||||||
| const ACTION_SCROLL_BOTTOM = `${EXT_NAME}:ScrollToBottom`; | const ACTION_SCROLL_BOTTOM = `${EXT_NAME}:ScrollToBottom`; | ||||||
| const ACTION_UPLOAD_STATE = `${EXT_NAME}:UploadStateFile`; | const ACTION_UPLOAD_STATE = `${EXT_NAME}:UploadStateFile`; | ||||||
|  | const ACTION_SLEEP = `${EXT_NAME}:Sleep`; | ||||||
|  | const ACTION_WAKEUP = `${EXT_NAME}:WakeUp`; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ class ConstMessage { | |||||||
| const URL_REG = getWebUrl(); | const URL_REG = getWebUrl(); | ||||||
| const MSG_ELEMENT_NOT_FOUND = new ConstMessage(1, "No element found for at least one selector, maybe it's not loaded yet"); | const MSG_ELEMENT_NOT_FOUND = new ConstMessage(1, "No element found for at least one selector, maybe it's not loaded yet"); | ||||||
| const MSG_URL_SKIPPED = new ConstMessage(100, "Skipped current URL"); | const MSG_URL_SKIPPED = new ConstMessage(100, "Skipped current URL"); | ||||||
|  | const MSG_USER_ABORT = new ConstMessage(100, "Tasks stopped by user."); | ||||||
|  |  | ||||||
| function saveFile(data, mimeType, fileName) { | function saveFile(data, mimeType, fileName) { | ||||||
|     fileName = fileName || document.title || "result"; |     fileName = fileName || document.title || "result"; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user