You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/preview-template/workbench/js/agent-console.js

409 lines
17 KiB

(function (global) {
'use strict';
var WorkbenchAgent = global.PWAgent;
if (!WorkbenchAgent) {
console.error('[调试工作台] agent-console 需先加载 agent-core');
return;
}
var CONSOLE_FLUSH_MS = 100;
WorkbenchAgent.prototype._hookConsole = function (on) {
var self = this;
if (on) {
if (this._consoleOrig) return;
this._consoleOrig = {
log: console.log.bind(console),
info: console.info.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
debug: console.debug.bind(console),
};
var wrap = function (level, fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
self._pushConsole(level, args);
return fn.apply(console, args);
};
};
console.log = wrap('log', this._consoleOrig.log);
console.info = wrap('info', this._consoleOrig.info);
console.warn = wrap('warn', this._consoleOrig.warn);
console.error = wrap('error', this._consoleOrig.error);
console.debug = wrap('debug', this._consoleOrig.debug);
this._bindConsoleErrors(true);
} else if (this._consoleOrig) {
this._bindConsoleErrors(false);
console.log = this._consoleOrig.log;
console.info = this._consoleOrig.info;
console.warn = this._consoleOrig.warn;
console.error = this._consoleOrig.error;
console.debug = this._consoleOrig.debug;
this._consoleOrig = null;
this._consolePost = false;
if (this._consoleFlushTimer) {
clearTimeout(this._consoleFlushTimer);
this._consoleFlushTimer = 0;
}
this._consoleBuf.length = 0;
}
};
/** 挂载/卸载 window 未捕获异常监听(随 console 通道启停) */
WorkbenchAgent.prototype._bindConsoleErrors = function (on) {
if (on) {
if (this._consoleErrBound) return;
window.addEventListener('error', this._boundConsoleError);
window.addEventListener('unhandledrejection', this._boundConsoleRejection);
this._consoleErrBound = true;
} else if (this._consoleErrBound) {
window.removeEventListener('error', this._boundConsoleError);
window.removeEventListener('unhandledrejection', this._boundConsoleRejection);
this._consoleErrBound = false;
}
};
/** 脚本运行时错误 → 控制台 error */
WorkbenchAgent.prototype._onConsoleError = function (ev) {
if (!ev) return;
var args = ev.error instanceof Error ? [ev.error] : [ev.message || 'Script error'];
var entry = this._serializeConsoleArgs('error', args);
if (ev.filename) {
entry.src = this._srcFromUrl(ev.filename, ev.lineno || 0, ev.colno || 0);
}
this._enqueueConsole(entry);
};
/** 未处理 Promise 拒绝 → 对齐浏览器 Uncaught (in promise) */
WorkbenchAgent.prototype._onConsoleRejection = function (ev) {
var reason = ev && ev.reason;
var args;
if (reason instanceof Error) {
args = ['Uncaught (in promise)', reason];
} else {
args = ['Uncaught (in promise) ' + String(reason)];
}
this._pushConsole('error', args);
};
WorkbenchAgent.prototype._pushConsole = function (level, args) {
this._enqueueConsole(this._serializeConsoleArgs(level, args));
};
WorkbenchAgent.prototype._enqueueConsole = function (entry) {
if (!entry) return;
if (this._consolePost && !this._hibernating) {
this._consoleBuf.push(entry);
var self = this;
if (!this._consoleFlushTimer) {
this._consoleFlushTimer = setTimeout(function () {
self._consoleFlushTimer = 0;
self._flushConsole();
}, CONSOLE_FLUSH_MS);
}
return;
}
// 未订阅时静默环缓冲,打开面板时再推送
this._consoleHist.push(entry);
if (this._consoleHist.length > 200) this._consoleHist.splice(0, this._consoleHist.length - 200);
};
WorkbenchAgent.prototype._flushConsole = function () {
if (!this._consolePost || !this._consoleBuf.length) {
this._consoleBuf.length = 0;
return;
}
var batch = this._consoleBuf.splice(0, this._consoleBuf.length);
this._post({ type: 'event', name: 'console.batch', data: { items: batch } });
};
/** 订阅时把未推送历史一次性推给壳 */
WorkbenchAgent.prototype._flushConsoleHist = function () {
if (!this._consolePost || !this._consoleHist.length) return;
var batch = this._consoleHist.splice(0, this._consoleHist.length);
this._post({ type: 'event', name: 'console.batch', data: { items: batch } });
this._consoleBuf.length = 0;
};
/**
* 将 console 参数序列化为可 postMessage 的富结构(保留 %c/%s 等,对象可展开)。
* 原始 console.* 仍会调用,浏览器 DevTools 打印不受影响。
*/
WorkbenchAgent.prototype._serializeConsoleArgs = function (level, args) {
var parts = [];
var plain = '';
var FMT_RE = /%(?:%|[csdifoO])/;
var rawStack = '';
try { rawStack = new Error().stack || ''; } catch (e0) { rawStack = ''; }
if (args.length && typeof args[0] === 'string' && FMT_RE.test(args[0])) {
var formatted = this._formatConsole(args);
parts = formatted.parts;
plain = formatted.plain;
// 对齐 Chrome:%c 只作用于格式串本身;其后附加参数用默认色、不加引号
for (var r = 0; r < formatted.rest.length; r++) {
var rv = formatted.rest[r];
if (typeof rv === 'string') {
parts.push({ kind: 'text', text: rv, plain: rv });
plain += (plain ? ' ' : '') + rv;
} else {
var rp = this._valuePart(rv);
parts.push(rp);
plain += (plain ? ' ' : '') + (rp.plain || '');
}
}
} else {
for (var i = 0; i < args.length; i++) {
// 顶层消息字符串不加引号(属性展开时仍用 string 带引号)
var p = this._valuePart(args[i], typeof args[i] === 'string');
parts.push(p);
plain += (i ? ' ' : '') + (p.plain || '');
}
}
var src = this._srcFromStack(rawStack);
var callStack = '';
for (var j = 0; j < parts.length; j++) {
if (parts[j] && parts[j].kind === 'error' && parts[j].stack) {
if (!src) src = this._srcFromStack(parts[j].stack);
callStack = parts[j].stack;
break;
}
}
if (!callStack && (level === 'warn' || level === 'error')) {
callStack = this._businessStack(rawStack);
}
return {
id: 'c' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
level: level,
t: Date.now(),
parts: parts,
plain: plain,
src: src,
stack: callStack || '',
};
};
/** 去掉 agent 帧后的业务堆栈文本 */
WorkbenchAgent.prototype._businessStack = function (stack) {
if (!stack) return '';
var lines = String(stack).split('\n');
var out = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var t = line.trim();
if (!t) continue;
if (t.indexOf('Error') === 0 && t.indexOf('at ') < 0) continue;
if (this._isAgentStackLine(t)) continue;
out.push(line);
}
return out.join('\n');
};
WorkbenchAgent.prototype._isAgentStackLine = function (line) {
return /agent(-\w+)?\.js|WorkbenchAgent|_pushConsole|_enqueueConsole|_serializeConsole|_captureConsoleSrc|_srcFromStack|_businessStack|_hookConsole|_onConsoleError|_onConsoleRejection|_valuePart|_formatConsole|_flushConsole/i.test(line);
};
/** 解析 stack:优先可读源文件名,跳过 agent / UUID 哈希帧 */
WorkbenchAgent.prototype._srcFromStack = function (stack) {
if (!stack) return null;
var lines = String(stack).split('\n');
var best = null;
var bestScore = -1;
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line || /^error$/i.test(line) || (line.indexOf('Error') === 0 && line.indexOf('at ') < 0)) continue;
if (this._isAgentStackLine(line)) continue;
var m = line.match(/\((.+?):(\d+):(\d+)\)\s*$/) || line.match(/at\s+(\S+?):(\d+):(\d+)\s*$/);
if (!m) continue;
var cand = this._srcFromUrl(m[1], parseInt(m[2], 10) || 0, parseInt(m[3], 10) || 0);
var score = this._scoreSrcFile(cand.file);
if (score > bestScore) {
bestScore = score;
best = cand;
}
}
return best;
};
/** 来源文件可读性评分(越高越优先展示) */
WorkbenchAgent.prototype._scoreSrcFile = function (file) {
var f = String(file || '');
if (!f || f === '(anonymous)') return 0;
if (/^[a-f0-9]{16,}$/i.test(f) || /^[a-f0-9-]{32,}$/i.test(f)) return 1;
if (/\.ts$/i.test(f)) return 50;
if (/\.js$/i.test(f) && /[a-zA-Z]/.test(f) && !/^[a-f0-9.-]+$/i.test(f.replace(/\.js$/i, ''))) return 40;
if (/\.(js|mjs|cjs)$/i.test(f)) return 20;
if (/[a-zA-Z]/.test(f)) return 10;
return 2;
};
/** URL + 行列 → 简短来源 */
WorkbenchAgent.prototype._srcFromUrl = function (url, line, col) {
var u = String(url || '');
var file = u.split('/').pop() || u;
var q = file.indexOf('?');
if (q >= 0) file = file.slice(0, q);
var h = file.indexOf('#');
if (h >= 0) file = file.slice(0, h);
if (!file) file = '(anonymous)';
var ln = line || 0;
return {
file: file,
line: ln,
col: col || 0,
text: ln ? (file + ':' + ln) : file,
};
};
WorkbenchAgent.prototype._formatConsole = function (args) {
var fmt = args[0];
var ai = 1;
var parts = [];
var plain = '';
var style = '';
var i = 0;
while (i < fmt.length) {
if (fmt.charAt(i) === '%' && i + 1 < fmt.length) {
var spec = fmt.charAt(i + 1);
if (spec === '%') {
parts.push({ kind: 'styled', text: '%', css: style });
plain += '%';
i += 2;
continue;
}
if (spec === 'c') {
style = typeof args[ai] === 'string' ? args[ai] : '';
ai++;
i += 2;
continue;
}
var val = ai < args.length ? args[ai] : undefined;
ai++;
var text = '';
if (spec === 's') text = val == null ? 'null' : String(val);
else if (spec === 'd' || spec === 'i') text = String(parseInt(val, 10) || 0);
else if (spec === 'f') text = String(Number(val));
else if (spec === 'o' || spec === 'O') {
var vp = this._valuePart(val);
parts.push(vp);
plain += vp.plain || '';
i += 2;
continue;
} else {
text = '%' + spec;
}
parts.push({ kind: 'styled', text: text, css: style });
plain += text;
i += 2;
continue;
}
var start = i;
while (i < fmt.length && fmt.charAt(i) !== '%') i++;
var lit = fmt.slice(start, i);
if (lit) {
parts.push({ kind: 'styled', text: lit, css: style });
plain += lit;
}
}
return { parts: parts, plain: plain, rest: args.slice(ai), style: style };
};
WorkbenchAgent.prototype._valuePart = function (value, bareString) {
if (value === null) return { kind: 'null', plain: 'null' };
var t = typeof value;
if (t === 'undefined') return { kind: 'undefined', plain: 'undefined' };
if (t === 'string') {
return bareString
? { kind: 'text', text: value, plain: value }
: { kind: 'string', text: value, plain: '"' + value + '"' };
}
if (t === 'number' || t === 'boolean' || t === 'bigint') {
return { kind: t, value: t === 'bigint' ? String(value) : value, plain: String(value) };
}
if (t === 'symbol') return { kind: 'symbol', text: String(value), plain: String(value) };
if (t === 'function') {
var fname = value.name || 'anonymous';
return { kind: 'function', name: fname, plain: 'ƒ ' + fname + '()' };
}
if (value instanceof Error) {
return {
kind: 'error',
name: value.name || 'Error',
message: value.message || '',
stack: value.stack || '',
plain: (value.name || 'Error') + ': ' + (value.message || ''),
};
}
var oid = 'o' + (this._consoleObjSeq++);
this._consoleObjs[oid] = value;
var isArray = Array.isArray(value);
var ctor = (value && value.constructor && value.constructor.name) || (isArray ? 'Array' : 'Object');
var preview = isArray ? ('Array(' + value.length + ')') : ctor;
try {
if (isArray) {
var bits = [];
for (var i = 0; i < Math.min(value.length, 5); i++) bits.push(briefVal(value[i]));
if (value.length > 5) bits.push('…');
preview += ' [' + bits.join(', ') + ']';
} else {
var keys = Object.keys(value);
var bits2 = [];
for (var j = 0; j < Math.min(keys.length, 4); j++) {
bits2.push(keys[j] + ': ' + briefVal(value[keys[j]]));
}
if (keys.length > 4) bits2.push('…');
preview += ' {' + bits2.join(', ') + '}';
}
} catch (e) { /* */ }
return {
kind: 'object',
oid: oid,
ctor: ctor,
isArray: isArray,
length: isArray ? value.length : undefined,
preview: preview,
plain: preview,
};
};
WorkbenchAgent.prototype._inspectConsoleObj = function (oid, path) {
var root = this._consoleObjs[oid];
if (!root) return { props: [] };
var target = root;
var segs = String(path || '').split('.').filter(Boolean);
for (var i = 0; i < segs.length; i++) {
try { target = target[segs[i]]; } catch (e) { return { props: [] }; }
}
var props = [];
var MAX = 40;
try {
if (Array.isArray(target)) {
for (var a = 0; a < Math.min(target.length, MAX); a++) {
props.push({ key: String(a), part: this._valuePart(target[a]) });
}
if (target.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } });
} else if (target && typeof target === 'object') {
var keys = Object.getOwnPropertyNames(target);
for (var k = 0; k < Math.min(keys.length, MAX); k++) {
var key = keys[k];
var child;
try { child = target[key]; } catch (err) { child = '⟨access error⟩'; }
props.push({ key: key, part: this._valuePart(child) });
}
if (keys.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } });
}
} catch (e2) { /* */ }
return { props: props };
};
function briefVal(v) {
if (v === null) return 'null';
var t = typeof v;
if (t === 'string') return '"' + (v.length > 24 ? v.slice(0, 24) + '…' : v) + '"';
if (t === 'number' || t === 'boolean' || t === 'undefined') return String(v);
if (t === 'function') return 'ƒ';
if (Array.isArray(v)) return 'Array(' + v.length + ')';
if (t === 'object') return '{…}';
return String(v);
}
})(window);