/// function initRuntimeSocket(hosts, port, id) { if (hosts == '' || port == '' || id == '') return Promise.resolve(null); return hosts .split(',') .reduce((promise, host) => { return promise.then((socket) => { if (socket != null) return Promise.resolve(socket); return tryConnectSocket(host, port, id); }); }, Promise.resolve(null)); } const SOCKET_TIMEOUT = 500; function tryConnectSocket(host, port, id) { return new Promise((resolve, reject) => { const socket = uni.connectSocket({ url: `ws://${host}:${port}/${id}`, multiple: true, // 支付宝小程序 是否开启多实例 fail() { resolve(null); }, }); const timer = setTimeout(() => { // @ts-expect-error socket.close({ code: 1006, reason: 'connect timeout', }); resolve(null); }, SOCKET_TIMEOUT); socket.onOpen((e) => { clearTimeout(timer); resolve(socket); }); socket.onClose((e) => { clearTimeout(timer); resolve(null); }); socket.onError((e) => { clearTimeout(timer); resolve(null); }); }); } const CONSOLE_TYPES = ['log', 'warn', 'error', 'info', 'debug']; const originalConsole = /*@__PURE__*/ CONSOLE_TYPES.reduce((methods, type) => { methods[type] = console[type].bind(console); return methods; }, {}); let sendError = null; // App.onError会监听到两类错误,一类是小程序自身抛出的,一类是 vue 的 errorHandler 触发的 // uni.onError 和 App.onError 会同时监听到错误(主要是App.onError监听之前的错误),所以需要用 Set 来去重 // uni.onError 会在 App.onError 上边同时增加监听,因为要监听 vue 的errorHandler // 目前 vue 的 errorHandler 仅会callHook('onError'),所以需要把uni.onError的也挂在 App.onError 上 const errorQueue = new Set(); const errorExtra = {}; function sendErrorMessages(errors) { if (sendError == null) { errors.forEach((error) => { errorQueue.add(error); }); return; } const data = errors .map((err) => { if (typeof err === 'string') { return err; } const isPromiseRejection = err && 'promise' in err && 'reason' in err; const prefix = isPromiseRejection ? 'UnhandledPromiseRejection: ' : ''; if (isPromiseRejection) { err = err.reason; } if (err instanceof Error && err.stack) { if (err.message && !err.stack.includes(err.message)) { return `${prefix}${err.message} ${err.stack}`; } return `${prefix}${err.stack}`; } if (typeof err === 'object' && err !== null) { try { return prefix + JSON.stringify(err); } catch (err) { return prefix + String(err); } } return prefix + String(err); }) .filter(Boolean); if (data.length > 0) { sendError(JSON.stringify(Object.assign({ type: 'error', data, }, errorExtra))); } } function setSendError(value, extra = {}) { sendError = value; Object.assign(errorExtra, extra); if (value != null && errorQueue.size > 0) { const errors = Array.from(errorQueue); errorQueue.clear(); sendErrorMessages(errors); } } function initOnError() { function onError(error) { try { // 小红书小程序 socket.send 时,会报错,onError错误信息为: // Cannot create property 'errMsg' on string 'taskId' // 导致陷入死循环 if (typeof PromiseRejectionEvent !== 'undefined' && error instanceof PromiseRejectionEvent && error.reason instanceof Error && error.reason.message && error.reason.message.includes(`Cannot create property 'errMsg' on string 'taskId`)) { return; } if (process.env.UNI_CONSOLE_KEEP_ORIGINAL) { originalConsole.error(error); } sendErrorMessages([error]); } catch (err) { originalConsole.error(err); } } if (typeof uni.onError === 'function') { uni.onError(onError); } if (typeof uni.onUnhandledRejection === 'function') { uni.onUnhandledRejection(onError); } return function offError() { if (typeof uni.offError === 'function') { uni.offError(onError); } if (typeof uni.offUnhandledRejection === 'function') { uni.offUnhandledRejection(onError); } }; } function formatMessage(type, args) { try { return { type, args: formatArgs(args), }; } catch (e) { // originalConsole.error(e) } return { type, args: [], }; } function formatArgs(args) { return args.map((arg) => formatArg(arg)); } function formatArg(arg, depth = 0) { if (depth >= 7) { return { type: 'object', value: '[Maximum depth reached]', }; } const type = typeof arg; switch (type) { case 'string': return formatString(arg); case 'number': return formatNumber(arg); case 'boolean': return formatBoolean(arg); case 'object': try { // 鸿蒙里边 object 可能包含 nativePtr 指针,该指针 typeof 是 object // 但是又不能访问上边的任意属性,否则会报:TypeError: Can not get Prototype on non ECMA Object // 所以这里需要捕获异常,防止报错 return formatObject(arg, depth); } catch (e) { return { type: 'object', value: { properties: [], }, }; } case 'undefined': return formatUndefined(); case 'function': return formatFunction(arg); case 'symbol': { return formatSymbol(arg); } case 'bigint': return formatBigInt(arg); } } function formatFunction(value) { return { type: 'function', value: `function ${value.name}() {}`, }; } function formatUndefined() { return { type: 'undefined', }; } function formatBoolean(value) { return { type: 'boolean', value: String(value), }; } function formatNumber(value) { return { type: 'number', value: String(value), }; } function formatBigInt(value) { return { type: 'bigint', value: String(value), }; } function formatString(value) { return { type: 'string', value, }; } function formatSymbol(value) { return { type: 'symbol', value: value.description, }; } function formatObject(value, depth) { if (value === null) { return { type: 'null', }; } { if (isComponentPublicInstance(value)) { return formatComponentPublicInstance(value, depth); } if (isComponentInternalInstance(value)) { return formatComponentInternalInstance(value, depth); } if (isUniElement(value)) { return formatUniElement(value, depth); } if (isCSSStyleDeclaration(value)) { return formatCSSStyleDeclaration(value, depth); } } if (Array.isArray(value)) { return { type: 'object', subType: 'array', value: { properties: value.map((v, i) => formatArrayElement(v, i, depth + 1)), }, }; } if (value instanceof Set) { return { type: 'object', subType: 'set', className: 'Set', description: `Set(${value.size})`, value: { entries: Array.from(value).map((v) => formatSetEntry(v, depth + 1)), }, }; } if (value instanceof Map) { return { type: 'object', subType: 'map', className: 'Map', description: `Map(${value.size})`, value: { entries: Array.from(value.entries()).map((v) => formatMapEntry(v, depth + 1)), }, }; } if (value instanceof Promise) { return { type: 'object', subType: 'promise', value: { properties: [], }, }; } if (value instanceof RegExp) { return { type: 'object', subType: 'regexp', value: String(value), className: 'Regexp', }; } if (value instanceof Date) { return { type: 'object', subType: 'date', value: String(value), className: 'Date', }; } if (value instanceof Error) { return { type: 'object', subType: 'error', value: value.message || String(value), className: value.name || 'Error', }; } let className = undefined; { const constructor = value.constructor; if (constructor) { // @ts-expect-error if (constructor.get$UTSMetadata$) { // @ts-expect-error className = constructor.get$UTSMetadata$().name; } } } let entries = Object.entries(value); if (isHarmonyBuilderParams(value)) { entries = entries.filter(([key]) => key !== 'modifier' && key !== 'nodeContent'); } return { type: 'object', className, value: { properties: entries.map((entry) => formatObjectProperty(entry[0], entry[1], depth + 1)), }, }; } function isHarmonyBuilderParams(value) { return value.modifier && value.modifier._attribute && value.nodeContent; } function isComponentPublicInstance(value) { return value.$ && isComponentInternalInstance(value.$); } function isComponentInternalInstance(value) { return value.type && value.uid != null && value.appContext; } function formatComponentPublicInstance(value, depth) { return { type: 'object', className: 'ComponentPublicInstance', value: { properties: Object.entries(value.$.type).map(([name, value]) => formatObjectProperty(name, value, depth + 1)), }, }; } function formatComponentInternalInstance(value, depth) { return { type: 'object', className: 'ComponentInternalInstance', value: { properties: Object.entries(value.type).map(([name, value]) => formatObjectProperty(name, value, depth + 1)), }, }; } function isUniElement(value) { return value.style && value.tagName != null && value.nodeName != null; } function formatUniElement(value, depth) { return { type: 'object', // 非 x 没有 UniElement 的概念 // className: 'UniElement', value: { properties: Object.entries(value) .filter(([name]) => [ 'id', 'tagName', 'nodeName', 'dataset', 'offsetTop', 'offsetLeft', 'style', ].includes(name)) .map(([name, value]) => formatObjectProperty(name, value, depth + 1)), }, }; } function isCSSStyleDeclaration(value) { return (typeof value.getPropertyValue === 'function' && typeof value.setProperty === 'function' && value.$styles); } function formatCSSStyleDeclaration(style, depth) { return { type: 'object', value: { properties: Object.entries(style.$styles).map(([name, value]) => formatObjectProperty(name, value, depth + 1)), }, }; } function formatObjectProperty(name, value, depth) { const result = formatArg(value, depth); result.name = name; return result; } function formatArrayElement(value, index, depth) { const result = formatArg(value, depth); result.name = `${index}`; return result; } function formatSetEntry(value, depth) { return { value: formatArg(value, depth), }; } function formatMapEntry(value, depth) { return { key: formatArg(value[0], depth), value: formatArg(value[1], depth), }; } let sendConsole = null; const messageQueue = []; const messageExtra = {}; const EXCEPTION_BEGIN_MARK = '---BEGIN:EXCEPTION---'; const EXCEPTION_END_MARK = '---END:EXCEPTION---'; function sendConsoleMessages(messages) { if (sendConsole == null) { messageQueue.push(...messages); return; } sendConsole(JSON.stringify(Object.assign({ type: 'console', data: messages, }, messageExtra))); } function setSendConsole(value, extra = {}) { sendConsole = value; Object.assign(messageExtra, extra); if (value != null && messageQueue.length > 0) { const messages = messageQueue.slice(); messageQueue.length = 0; sendConsoleMessages(messages); } } const atFileRegex = /^\s*at\s+[\w/./-]+:\d+$/; function rewriteConsole() { function wrapConsole(type) { return function (...args) { const originalArgs = [...args]; if (originalArgs.length) { const maybeAtFile = originalArgs[originalArgs.length - 1]; // 移除最后的 at pages/index/index.uvue:6 if (typeof maybeAtFile === 'string' && atFileRegex.test(maybeAtFile)) { originalArgs.pop(); } } if (process.env.UNI_CONSOLE_KEEP_ORIGINAL) { originalConsole[type](...originalArgs); } if (type === 'error' && args.length === 1) { const arg = args[0]; if (typeof arg === 'string' && arg.startsWith(EXCEPTION_BEGIN_MARK)) { const startIndex = EXCEPTION_BEGIN_MARK.length; const endIndex = arg.length - EXCEPTION_END_MARK.length; sendErrorMessages([arg.slice(startIndex, endIndex)]); return; } else if (arg instanceof Error) { sendErrorMessages([arg]); return; } } sendConsoleMessages([formatMessage(type, args)]); }; } // 百度小程序不允许赋值,所以需要判断是否可写 if (isConsoleWritable()) { CONSOLE_TYPES.forEach((type) => { console[type] = wrapConsole(type); }); return function restoreConsole() { CONSOLE_TYPES.forEach((type) => { console[type] = originalConsole[type]; }); }; } else { { if (typeof uni !== 'undefined' && uni.__f__) { const oldLog = uni.__f__; if (oldLog) { // 重写 uni.__f__ 方法,这样的话,仅能打印开发者代码里的日志,其他没有被重写为__f__的日志将无法打印(比如uni-app框架、小程序框架等) uni.__f__ = function (...args) { const [type, filename, ...rest] = args; // 原始日志移除 filename oldLog(type, '', ...rest); sendConsoleMessages([formatMessage(type, [...rest, filename])]); }; return function restoreConsole() { uni.__f__ = oldLog; }; } } } } return function restoreConsole() { }; } function isConsoleWritable() { const value = console.log; const sym = Symbol(); try { // @ts-expect-error console.log = sym; } catch (ex) { return false; } // @ts-expect-error const isWritable = console.log === sym; console.log = value; return isWritable; } function initRuntimeSocketService() { const hosts = process.env.UNI_SOCKET_HOSTS; const port = process.env.UNI_SOCKET_PORT; const id = process.env.UNI_SOCKET_ID; if (!hosts || !port || !id) return Promise.resolve(false); // 百度小程序需要延迟初始化,不然会存在循环引用问题vendor.js const lazy = typeof swan !== 'undefined'; // 重写需要同步,避免丢失早期日志信息 let restoreError = lazy ? () => { } : initOnError(); let restoreConsole = lazy ? () => { } : rewriteConsole(); // 百度小程序需要异步初始化,不然调用 uni.connectSocket 会循环引入vendor.js return Promise.resolve().then(() => { if (lazy) { restoreError = initOnError(); restoreConsole = rewriteConsole(); } return initRuntimeSocket(hosts, port, id).then((socket) => { if (!socket) { restoreError(); restoreConsole(); originalConsole.error(wrapError('开发模式下日志通道建立 socket 连接失败。')); // @ts-expect-error { originalConsole.error(wrapError('小程序平台,请勾选不校验合法域名配置。')); } originalConsole.error(wrapError('如果是运行到真机,请确认手机与电脑处于同一网络。')); return false; } // @ts-expect-error { initMiniProgramGlobalFlag(); } socket.onClose(() => { if (process.env.UNI_DEBUG) { originalConsole.log(`uni-app:[${Date.now()}][socket]`, 'connect close and restore'); } // @ts-expect-error { originalConsole.error(wrapError('开发模式下日志通道 socket 连接关闭,请在 HBuilderX 中重新运行。')); } restoreError(); restoreConsole(); }); setSendConsole((data) => { if (process.env.UNI_DEBUG) { originalConsole.log(`uni-app:[${Date.now()}][console]`, data); } socket.send({ data, }); }); setSendError((data) => { if (process.env.UNI_DEBUG) { originalConsole.log(`uni-app:[${Date.now()}][error]`, data); } socket.send({ data, }); }); return true; }); }); } const ERROR_CHAR = '\u200C'; function wrapError(error) { return `${ERROR_CHAR}${error}${ERROR_CHAR}`; } function initMiniProgramGlobalFlag() { if (typeof wx !== 'undefined') { // @ts-expect-error wx.__uni_console__ = true; // @ts-expect-error } else if (typeof my !== 'undefined') { // @ts-expect-error my.__uni_console__ = true; } else if (typeof tt !== 'undefined') { tt.__uni_console__ = true; } else if (typeof swan !== 'undefined') { swan.__uni_console__ = true; } else if (typeof qq !== 'undefined') { qq.__uni_console__ = true; } else if (typeof ks !== 'undefined') { ks.__uni_console__ = true; } else if (typeof jd !== 'undefined') { jd.__uni_console__ = true; } else if (typeof xhs !== 'undefined') { xhs.__uni_console__ = true; } else if (typeof has !== 'undefined') { has.__uni_console__ = true; } else if (typeof qa !== 'undefined') { qa.__uni_console__ = true; } } initRuntimeSocketService(); export { initRuntimeSocketService };