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