1
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
// amazon_top_list:Amazon TOP 榜单抓取(Best Sellers / New Releases / Movers & Shakers)
|
// amazon_top_list:Amazon TOP 榜单抓取(Best Sellers / New Releases / Movers & Shakers)
|
||||||
|
|
||||||
import { openTab } from '../libs/tabs.js';
|
import { create_tab_task } from '../libs/tabs.js';
|
||||||
import { execute_script } from '../libs/inject.js';
|
import { injected_amazon_search_list } from '../injected/amazon_search_list.js';
|
||||||
|
|
||||||
export function amazon_search_list(data, sendResponse) {
|
export function amazon_search_list(data, sendResponse) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const category_keyword = (data && data.category_keyword) ? String(data.category_keyword).trim() : '';
|
const category_keyword = (data && data.category_keyword) ? String(data.category_keyword).trim() : '';
|
||||||
|
|
||||||
const keyword = category_keyword || '野餐包';
|
const keyword = category_keyword || '野餐包';
|
||||||
@@ -24,159 +24,40 @@ export function amazon_search_list(data, sendResponse) {
|
|||||||
|
|
||||||
let times = 0;
|
let times = 0;
|
||||||
|
|
||||||
openTab({
|
const send_action = (action, payload) => {
|
||||||
url,
|
if (typeof sendResponse === 'function') {
|
||||||
latest: false,
|
sendResponse({ action, data: payload });
|
||||||
top: 20,
|
sendResponse.log && sendResponse.log(payload);
|
||||||
left: 20,
|
}
|
||||||
width: 1440,
|
};
|
||||||
height: 900,
|
|
||||||
target: '__amazon_search_list',
|
|
||||||
|
|
||||||
tabError(tab, details) {
|
const tab_task = create_tab_task(url)
|
||||||
const result = {
|
.set_latest(false)
|
||||||
code: 30,
|
.set_bounds({ top: 20, left: 20, width: 1440, height: 900 })
|
||||||
status: false,
|
.set_target('__amazon_search_list');
|
||||||
message: (details && (details.message || details.statusLine || details.error)) || 'tab error',
|
|
||||||
data: null,
|
|
||||||
documentURI: details && details.url,
|
|
||||||
};
|
|
||||||
|
|
||||||
sendResponse && sendResponse({ action: 'amazon_search_list', data: result });
|
try {
|
||||||
sendResponse && sendResponse.log && sendResponse.log(result);
|
const tab = await tab_task.open_async();
|
||||||
|
|
||||||
if (tab && tab.remove) {
|
await tab.execute_script(injected_amazon_search_list, [{ url, category_keyword }], 'document_idle');
|
||||||
tab.remove(1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
reject(new Error(result.message));
|
const result = { code: 0, status: true, message: 'ok', data: { tab_id: tab.id, url, category_keyword } };
|
||||||
},
|
send_action('amazon_search_list', result);
|
||||||
|
resolve({ tab_id: tab.id, url, category_keyword });
|
||||||
|
|
||||||
tabUpdated(tab, details) {
|
// 可选:自动关 tab(默认不关,方便调试)
|
||||||
if (times > 0) return;
|
// tab && tab.remove && tab.remove(3500);
|
||||||
times += 1;
|
} catch (err) {
|
||||||
|
const result = {
|
||||||
const tab_id = tab && tab.id;
|
code: 30,
|
||||||
if (!tab_id) {
|
status: false,
|
||||||
return reject(new Error('tab.id 为空'));
|
message: (err && err.message) || String(err),
|
||||||
}
|
data: null,
|
||||||
|
documentURI: url,
|
||||||
execute_script(
|
};
|
||||||
tab_id,
|
send_action('amazon_search_list', result);
|
||||||
{
|
reject(err);
|
||||||
fn: (params) => {
|
}
|
||||||
const action = 'amazon_search_list';
|
|
||||||
const start_url = params && params.url ? String(params.url) : location.href;
|
|
||||||
const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : '';
|
|
||||||
|
|
||||||
function send(action_name, data) {
|
|
||||||
try {
|
|
||||||
chrome.runtime.sendMessage({ type: 'push', action: action_name, data });
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function abs_url(href) {
|
|
||||||
try {
|
|
||||||
return new URL(href, location.origin).toString();
|
|
||||||
} catch (_) {
|
|
||||||
return href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_asin_from_url(url) {
|
|
||||||
if (!url || typeof url !== 'string') return null;
|
|
||||||
const m = url.match(/\/dp\/([A-Z0-9]{10})/i) || url.match(/\/gp\/product\/([A-Z0-9]{10})/i);
|
|
||||||
return m ? m[1].toUpperCase() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extract_results() {
|
|
||||||
const items = [];
|
|
||||||
const nodes = document.querySelectorAll('div.s-main-slot div[data-component-type="s-search-result"]');
|
|
||||||
nodes.forEach((el, idx) => {
|
|
||||||
const asin = (el.getAttribute('data-asin') || '').trim() || null;
|
|
||||||
|
|
||||||
const title_el = el.querySelector('h2 span') || el.querySelector('h2');
|
|
||||||
const title = title_el ? title_el.textContent.trim() : null;
|
|
||||||
|
|
||||||
// 搜索页有的标题在 h2 里,不一定包在 <a> 里;链接一般在 a[href*="/dp/"]
|
|
||||||
const a = el.querySelector('a[href*="/dp/"], a[href*="/gp/product/"]');
|
|
||||||
const href = a ? a.getAttribute('href') : null;
|
|
||||||
const url = href ? abs_url(href) : null;
|
|
||||||
|
|
||||||
|
|
||||||
const price_el = el.querySelector('span.a-price > span.a-offscreen');
|
|
||||||
const price = price_el ? price_el.textContent.trim() : null;
|
|
||||||
|
|
||||||
const rating_el = el.querySelector('span.a-icon-alt');
|
|
||||||
const rating_text = rating_el ? rating_el.textContent.trim() : null;
|
|
||||||
|
|
||||||
// 评论数在不同语言/布局下 class 不稳定,这里做弱依赖
|
|
||||||
const review_count_el =
|
|
||||||
el.querySelector('span[aria-label$="ratings"]') ||
|
|
||||||
el.querySelector('span[aria-label$="rating"]') ||
|
|
||||||
el.querySelector('span[aria-label$="评价"]') ||
|
|
||||||
el.querySelector('span[aria-label$="评分"]');
|
|
||||||
const review_count_text = review_count_el ? review_count_el.textContent.trim() : null;
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
index: idx + 1,
|
|
||||||
asin: asin || parse_asin_from_url(url),
|
|
||||||
title,
|
|
||||||
url,
|
|
||||||
price,
|
|
||||||
rating_text,
|
|
||||||
review_count_text,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = extract_results();
|
|
||||||
|
|
||||||
send(action, {
|
|
||||||
stage: 'start',
|
|
||||||
start_url,
|
|
||||||
href: location.href,
|
|
||||||
category_keyword,
|
|
||||||
total: items.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 不逐条推送,直接一次性推完整列表
|
|
||||||
send(action, {
|
|
||||||
stage: 'list',
|
|
||||||
category_keyword,
|
|
||||||
total: items.length,
|
|
||||||
items,
|
|
||||||
});
|
|
||||||
|
|
||||||
send(action, { stage: 'end', category_keyword, total: items.length });
|
|
||||||
},
|
|
||||||
args: [{ url, category_keyword }],
|
|
||||||
},
|
|
||||||
'document_idle',
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
const result = {
|
|
||||||
code: 0,
|
|
||||||
status: true,
|
|
||||||
message: 'ok',
|
|
||||||
data: { tab_id, url, category_keyword },
|
|
||||||
};
|
|
||||||
sendResponse && sendResponse({ action: 'amazon_search_list', data: result });
|
|
||||||
sendResponse && sendResponse.log && sendResponse.log(result);
|
|
||||||
resolve({ tab_id, url, category_keyword });
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (tab && tab.remove) {
|
|
||||||
tab.remove(500);
|
|
||||||
}
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,17 +101,17 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
|
|
||||||
const action_send_response = create_action_send_response(sender);
|
const action_send_response = create_action_send_response(sender);
|
||||||
|
|
||||||
Promise.resolve()
|
(async () => {
|
||||||
.then(() => fn(message.data || {}, action_send_response))
|
try {
|
||||||
.then((res) => {
|
const res = await fn(message.data || {}, action_send_response);
|
||||||
emit_ui_event('response', { type: 'response', request_id, ok: true, data: res, sender });
|
emit_ui_event('response', { type: 'response', request_id, ok: true, data: res, sender });
|
||||||
sendResponse({ ok: true, data: res, request_id });
|
sendResponse({ ok: true, data: res, request_id });
|
||||||
})
|
} catch (err) {
|
||||||
.catch((err) => {
|
|
||||||
const error = (err && err.message) || String(err);
|
const error = (err && err.message) || String(err);
|
||||||
emit_ui_event('response', { type: 'response', request_id, ok: false, error, sender });
|
emit_ui_event('response', { type: 'response', request_id, ok: false, error, sender });
|
||||||
sendResponse({ ok: false, error, request_id });
|
sendResponse({ ok: false, error, request_id });
|
||||||
});
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -112,8 +112,9 @@
|
|||||||
if (options.body !== undefined) detail.data = options.body;
|
if (options.body !== undefined) detail.data = options.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
return target(...args)
|
return (async () => {
|
||||||
.then(async (response) => {
|
try {
|
||||||
|
const response = await target(...args);
|
||||||
detail.ok = true;
|
detail.ok = true;
|
||||||
detail.message = 'ok';
|
detail.message = 'ok';
|
||||||
const cloneResponse = response.clone();
|
const cloneResponse = response.clone();
|
||||||
@@ -122,7 +123,6 @@
|
|||||||
detail.redirected = cloneResponse.redirected;
|
detail.redirected = cloneResponse.redirected;
|
||||||
detail.url = cloneResponse.url;
|
detail.url = cloneResponse.url;
|
||||||
detail.text = await cloneResponse.text();
|
detail.text = await cloneResponse.text();
|
||||||
detail.response = response.clone();
|
|
||||||
detail.responseContentType = response.headers.get('Content-Type')?.toLowerCase();
|
detail.responseContentType = response.headers.get('Content-Type')?.toLowerCase();
|
||||||
if (
|
if (
|
||||||
detail.responseContentType &&
|
detail.responseContentType &&
|
||||||
@@ -131,14 +131,12 @@
|
|||||||
detail.json = await response.clone().json();
|
detail.json = await response.clone().json();
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
|
||||||
detail.ok = false;
|
detail.ok = false;
|
||||||
detail.error = error;
|
detail.error = error;
|
||||||
detail.message = error.message;
|
detail.message = error.message;
|
||||||
throw error;
|
throw error;
|
||||||
})
|
} finally {
|
||||||
.finally(() => {
|
|
||||||
const payload = {
|
const payload = {
|
||||||
TAG: detail.TAG,
|
TAG: detail.TAG,
|
||||||
method: detail.method,
|
method: detail.method,
|
||||||
@@ -154,7 +152,8 @@
|
|||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('__REQUEST_DONE', { detail: payload }));
|
window.dispatchEvent(new CustomEvent('__REQUEST_DONE', { detail: payload }));
|
||||||
detail = null;
|
detail = null;
|
||||||
});
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
75
mv2_simple_crx/src/injected/amazon_search_list.js
Normal file
75
mv2_simple_crx/src/injected/amazon_search_list.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// 注入到页面的 Amazon 搜索列表解析逻辑
|
||||||
|
|
||||||
|
export function injected_amazon_search_list(params) {
|
||||||
|
const action = 'amazon_search_list';
|
||||||
|
const start_url = params && params.url ? String(params.url) : location.href;
|
||||||
|
const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : '';
|
||||||
|
|
||||||
|
function send(action_name, data) {
|
||||||
|
try {
|
||||||
|
chrome.runtime.sendMessage({ type: 'push', action: action_name, data });
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function abs_url(href) {
|
||||||
|
try {
|
||||||
|
return new URL(href, location.origin).toString();
|
||||||
|
} catch (_) {
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_asin_from_url(url) {
|
||||||
|
if (!url || typeof url !== 'string') return null;
|
||||||
|
const m = url.match(/\/dp\/([A-Z0-9]{10})/i) || url.match(/\/gp\/product\/([A-Z0-9]{10})/i);
|
||||||
|
return m ? m[1].toUpperCase() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extract_results() {
|
||||||
|
const items = [];
|
||||||
|
const nodes = document.querySelectorAll('div.s-main-slot div[data-component-type="s-search-result"]');
|
||||||
|
nodes.forEach((el, idx) => {
|
||||||
|
const asin = (el.getAttribute('data-asin') || '').trim() || null;
|
||||||
|
|
||||||
|
const title_el = el.querySelector('h2 span') || el.querySelector('h2');
|
||||||
|
const title = title_el ? title_el.textContent.trim() : null;
|
||||||
|
|
||||||
|
const a = el.querySelector('a[href*="/dp/"], a[href*="/gp/product/"]');
|
||||||
|
const href = a ? a.getAttribute('href') : null;
|
||||||
|
const url = href ? abs_url(href) : null;
|
||||||
|
|
||||||
|
const price_el = el.querySelector('span.a-price > span.a-offscreen');
|
||||||
|
const price = price_el ? price_el.textContent.trim() : null;
|
||||||
|
|
||||||
|
const rating_el = el.querySelector('span.a-icon-alt');
|
||||||
|
const rating_text = rating_el ? rating_el.textContent.trim() : null;
|
||||||
|
|
||||||
|
const review_count_el =
|
||||||
|
el.querySelector('span[aria-label$="ratings"]') ||
|
||||||
|
el.querySelector('span[aria-label$="rating"]') ||
|
||||||
|
el.querySelector('span[aria-label$="评价"]') ||
|
||||||
|
el.querySelector('span[aria-label$="评分"]');
|
||||||
|
const review_count_text = review_count_el ? review_count_el.textContent.trim() : null;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
index: idx + 1,
|
||||||
|
asin: asin || parse_asin_from_url(url),
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
price,
|
||||||
|
rating_text,
|
||||||
|
review_count_text,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = extract_results();
|
||||||
|
|
||||||
|
send(action, { stage: 'start', start_url, href: location.href, category_keyword, total: items.length });
|
||||||
|
send(action, { stage: 'list', category_keyword, total: items.length, items });
|
||||||
|
send(action, { stage: 'end', category_keyword, total: items.length });
|
||||||
|
}
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
// executeScript:MV2 使用 chrome.tabs.executeScript
|
// executeScript:MV2 使用 chrome.tabs.executeScript
|
||||||
|
|
||||||
function normalize_code(code) {
|
function build_code(fn, args) {
|
||||||
// 支持:直接传函数
|
if (typeof fn === 'function') {
|
||||||
if (typeof code === 'function') {
|
if (Array.isArray(args) && args.length) {
|
||||||
return `(${code.toString()})();`;
|
return `(${fn.toString()}).apply(null, ${JSON.stringify(args)});`;
|
||||||
|
}
|
||||||
|
return `(${fn.toString()})();`;
|
||||||
}
|
}
|
||||||
|
return fn;
|
||||||
// 支持:传 { fn, args },这样 action 文件里不需要拼字符串也能传参
|
|
||||||
if (code && typeof code === 'object' && typeof code.fn === 'function') {
|
|
||||||
const args = Array.isArray(code.args) ? code.args : [];
|
|
||||||
return `(${code.fn.toString()}).apply(null, ${JSON.stringify(args)});`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execute_script(tab_id, code, run_at) {
|
// execute_script(tabId, fn, args?, runAt?)
|
||||||
|
export function execute_script(tab_id, fn, args, run_at) {
|
||||||
run_at = run_at || 'document_idle';
|
run_at = run_at || 'document_idle';
|
||||||
code = normalize_code(code);
|
const code = build_code(fn, args);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
chrome.tabs.executeScript(
|
chrome.tabs.executeScript(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// openTab:MV2 版本(极简 + 回调风格)
|
// openTab:MV2 版本(极简 + 回调风格)
|
||||||
|
|
||||||
|
import { execute_script } from './inject.js';
|
||||||
|
|
||||||
function attach_tab_helpers(tab) {
|
function attach_tab_helpers(tab) {
|
||||||
if (!tab) return tab;
|
if (!tab) return tab;
|
||||||
|
|
||||||
@@ -10,6 +12,10 @@ function attach_tab_helpers(tab) {
|
|||||||
}, Math.max(0, delay_ms));
|
}, Math.max(0, delay_ms));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tab.execute_script = async function execute_script_on_tab(fn, args, run_at) {
|
||||||
|
return await execute_script(tab.id, fn, args, run_at);
|
||||||
|
};
|
||||||
|
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,24 +61,46 @@ export function close_tab(tab_id, delay_ms) {
|
|||||||
}, Math.max(0, delay_ms));
|
}, Math.max(0, delay_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容你原来的调用风格:openTab({ url, ..., tabError(tab, details), tabUpdated(tab, details) })
|
// openTab 任务对象:用对象绑定方法,减少重复参数
|
||||||
export function openTab(options) {
|
export function create_tab_task(url) {
|
||||||
options = options && typeof options === 'object' ? options : {};
|
const task = {
|
||||||
const url = options.url;
|
url,
|
||||||
|
latest: false,
|
||||||
|
top: 20,
|
||||||
|
left: 20,
|
||||||
|
width: 1440,
|
||||||
|
height: 900,
|
||||||
|
target: null,
|
||||||
|
active: true,
|
||||||
|
// 你期望的写法:tab_task.on_updated = () => {}
|
||||||
|
on_error: null,
|
||||||
|
on_updated: null,
|
||||||
|
set_bounds(bounds) {
|
||||||
|
bounds = bounds && typeof bounds === 'object' ? bounds : {};
|
||||||
|
if (Object.prototype.hasOwnProperty.call(bounds, 'top')) this.top = bounds.top;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(bounds, 'left')) this.left = bounds.left;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(bounds, 'width')) this.width = bounds.width;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(bounds, 'height')) this.height = bounds.height;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
set_target(target) {
|
||||||
|
this.target = target || null;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
set_latest(latest) {
|
||||||
|
this.latest = !!latest;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
set_active(active) {
|
||||||
|
this.active = active !== false;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
async open_async() {
|
||||||
|
// 直接返回 tab 对象(带 remove / execute_script)
|
||||||
|
const { tab } = await open_tab(this.url, { active: this.active !== false });
|
||||||
|
return tab;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const tabError = typeof options.tabError === 'function' ? options.tabError : () => void 0;
|
return task;
|
||||||
const tabUpdated = typeof options.tabUpdated === 'function' ? options.tabUpdated : () => void 0;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
tabError(null, { error: 'url 不能为空' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_tab(url, { active: options.active !== false })
|
|
||||||
.then(({ tab }) => {
|
|
||||||
tabUpdated(tab, { status: 'complete' });
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
tabError(null, { error: err.message || String(err) });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,6 @@
|
|||||||
<div class="card_title">响应</div>
|
<div class="card_title">响应</div>
|
||||||
<pre id="last_response" class="pre"></pre>
|
<pre id="last_response" class="pre"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card span2">
|
|
||||||
<div class="card_title">推送事件(实时)</div>
|
|
||||||
<pre id="event_log" class="pre pre_scroll"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ const btn_run_el = document.getElementById('btn_run');
|
|||||||
const btn_clear_el = document.getElementById('btn_clear');
|
const btn_clear_el = document.getElementById('btn_clear');
|
||||||
const btn_bg_reload_el = document.getElementById('btn_bg_reload');
|
const btn_bg_reload_el = document.getElementById('btn_bg_reload');
|
||||||
const last_response_el = document.getElementById('last_response');
|
const last_response_el = document.getElementById('last_response');
|
||||||
const event_log_el = document.getElementById('event_log');
|
|
||||||
let actions_meta = {};
|
let actions_meta = {};
|
||||||
|
const ui_state = { events: [] };
|
||||||
|
|
||||||
function now_time() {
|
function now_time() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
@@ -25,34 +25,40 @@ function set_last_response(obj) {
|
|||||||
last_response_el.textContent = JSON.stringify(obj, null, 2);
|
last_response_el.textContent = JSON.stringify(obj, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function append_event_line(obj) {
|
function render_state() {
|
||||||
const line = `[${now_time()}] ${JSON.stringify(obj)}`;
|
// 只在一个窗口展示所有数据,方便查看
|
||||||
event_log_el.textContent = (event_log_el.textContent ? event_log_el.textContent + '\n' : '') + line;
|
last_response_el.textContent = JSON.stringify(ui_state, null, 2);
|
||||||
event_log_el.scrollTop = event_log_el.scrollHeight;
|
}
|
||||||
|
|
||||||
|
function push_event(obj) {
|
||||||
|
ui_state.events.push({ ts: now_time(), ...obj });
|
||||||
|
// 简单限长,避免无限增长
|
||||||
|
if (ui_state.events.length > 300) {
|
||||||
|
ui_state.events.splice(0, ui_state.events.length - 300);
|
||||||
|
}
|
||||||
|
render_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
btn_run_el.addEventListener('click', () => {
|
btn_run_el.addEventListener('click', () => {
|
||||||
const action = action_name_el.value;
|
const action = action_name_el.value;
|
||||||
const params = safe_json_parse(action_params_el.value || '{}');
|
const params = safe_json_parse(action_params_el.value || '{}');
|
||||||
|
|
||||||
append_event_line({ type: 'call', action, params });
|
push_event({ type: 'call', action, params });
|
||||||
set_last_response({ running: true, action, params });
|
|
||||||
|
|
||||||
chrome.runtime.sendMessage({ action, data: params }, (res) => {
|
chrome.runtime.sendMessage({ action, data: params }, (res) => {
|
||||||
set_last_response(res);
|
push_event({ type: 'reply', action, res });
|
||||||
append_event_line({ type: 'response', action, res });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
btn_clear_el.addEventListener('click', () => {
|
btn_clear_el.addEventListener('click', () => {
|
||||||
last_response_el.textContent = '';
|
ui_state.events = [];
|
||||||
event_log_el.textContent = '';
|
render_state();
|
||||||
});
|
});
|
||||||
|
|
||||||
btn_bg_reload_el.addEventListener('click', () => {
|
btn_bg_reload_el.addEventListener('click', () => {
|
||||||
append_event_line({ type: 'ui', action: 'reload_background' });
|
push_event({ type: 'ui', action: 'reload_background' });
|
||||||
chrome.runtime.sendMessage({ action: 'reload_background', data: {} }, (res) => {
|
chrome.runtime.sendMessage({ action: 'reload_background', data: {} }, (res) => {
|
||||||
append_event_line({ type: 'ui', action: 'reload_background_done', res });
|
push_event({ type: 'ui', action: 'reload_background_done', res });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,17 +109,17 @@ chrome.runtime.onMessage.addListener((message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.event_name === 'push') {
|
if (message.event_name === 'push') {
|
||||||
append_event_line({ type: 'push', payload: message.payload });
|
push_event({ type: 'push', payload: message.payload });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.event_name === 'request') {
|
if (message.event_name === 'request') {
|
||||||
append_event_line({ type: 'request', payload: message.payload });
|
push_event({ type: 'request', payload: message.payload });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.event_name === 'response') {
|
if (message.event_name === 'response') {
|
||||||
append_event_line({ type: 'bg_response', payload: message.payload });
|
push_event({ type: 'bg_response', payload: message.payload });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -126,4 +132,5 @@ chrome.runtime.sendMessage({ action: 'meta_actions', data: {} }, (res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
append_event_line({ type: 'ready', hint: '点击右侧/上方执行按钮开始' });
|
render_state();
|
||||||
|
push_event({ type: 'ready', hint: '点击右侧/上方执行按钮开始' });
|
||||||
|
|||||||
Reference in New Issue
Block a user