This commit is contained in:
Your Name
2026-08-11 17:41:36 +08:00
parent cfe4c82c90
commit 03fe4ddf9d
18771 changed files with 3617239 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
import type { Plugin } from 'vite'
import fs from 'fs-extra'
import path from 'path'
import {
defineUniMainJsPlugin,
isEnableConsole,
normalizePath,
resolveBuiltIn,
} from '@dcloudio/uni-cli-shared'
const uniConsoleRuntimePlugin = (): Plugin => {
return {
name: 'uni:console:runtime',
config() {
const isX = process.env.UNI_APP_X === 'true'
const isProd = process.env.NODE_ENV === 'production'
let keepOriginal = true
if (
process.env.UNI_PLATFORM == 'mp-harmony' ||
process.env.UNI_PLATFORM === 'app-harmony'
) {
keepOriginal = false
}
const webviewEvalJsCode =
isX && process.env.UNI_UTS_PLATFORM === 'app-android'
? fs.readFileSync(
path.join(__dirname, '../dist/__uniwebview.js'),
'utf-8'
)
: ''
return {
define: {
'process.env.UNI_CONSOLE_KEEP_ORIGINAL': process.env
.UNI_CONSOLE_KEEP_ORIGINAL
? process.env.UNI_CONSOLE_KEEP_ORIGINAL === 'true'
: keepOriginal,
'process.env.UNI_SOCKET_HOSTS': JSON.stringify(
isProd ? '' : process.env.UNI_SOCKET_HOSTS
),
'process.env.UNI_SOCKET_PORT': JSON.stringify(
isProd ? '' : process.env.UNI_SOCKET_PORT
),
'process.env.UNI_SOCKET_ID': JSON.stringify(
isProd ? '' : process.env.UNI_SOCKET_ID
),
'process.env.UNI_CONSOLE_WEBVIEW_EVAL_JS_CODE':
JSON.stringify(webviewEvalJsCode),
},
}
},
}
}
export default () => {
return [
uniConsoleRuntimePlugin(),
defineUniMainJsPlugin((opts) => {
let hasRuntimeSocket = isEnableConsole()
const isX = process.env.UNI_APP_X === 'true'
// 基座类型为custom时,不启用运行时socket
// 需要判断自定义基座是否包含socket模块,有的话才可以启用
if (isX && process.env.UNI_PLATFORM === 'app') {
if (process.env.HX_USE_BASE_TYPE === 'custom') {
hasRuntimeSocket = false
}
}
let uniConsolePath = resolveBuiltIn(
path.join(
'@dcloudio/uni-console',
`dist/${
(process.env.UNI_PLATFORM || '').startsWith('mp-') ? 'mp' : 'index'
}.esm.js`
)
)
if (isX) {
if (process.env.UNI_UTS_PLATFORM === 'app-android') {
uniConsolePath = resolveBuiltIn(
path.join('@dcloudio/uni-console', 'src/runtime/app/index.ts')
)
} else if (process.env.UNI_UTS_PLATFORM === 'app-ios') {
uniConsolePath = resolveBuiltIn(
path.join('@dcloudio/uni-console', 'dist/app.esm.js')
)
}
} else {
if (process.env.UNI_PLATFORM === 'app-harmony') {
uniConsolePath = resolveBuiltIn(
path.join('@dcloudio/uni-console', 'dist/harmony.jsvm.esm.js')
)
}
}
return {
name: 'uni:console-main-js',
enforce:
// android需要提前,不然拿到的code是解析后的仅保留import语句的
process.env.UNI_UTS_PLATFORM === 'app-android' ? 'pre' : 'post',
transform(code: string, id: string) {
if (!hasRuntimeSocket) {
return
}
if (!opts.filter(id)) {
return
}
return {
// 采用绝对路径引入,此时,tsc失效,代码里需要自己处理好各种类型问题
code: `import '${normalizePath(uniConsolePath)}';${code}`,
map: null,
}
},
}
}),
]
}
@@ -0,0 +1,34 @@
import { initRuntimeSocket } from './socket'
export function initRuntimeSocketService(): Promise<boolean> {
const hosts: string = process.env.UNI_SOCKET_HOSTS
const port: string = process.env.UNI_SOCKET_PORT
const id: string = process.env.UNI_SOCKET_ID
if (hosts == '' || port == '' || id == '') return Promise.resolve(false)
let socketTask: SocketTask | null = null
__registerWebViewUniConsole(
(): string => {
return process.env.UNI_CONSOLE_WEBVIEW_EVAL_JS_CODE
},
(data: string) => {
socketTask?.send({
data,
} as SendSocketMessageOptions)
}
)
return Promise.resolve()
.then((): Promise<boolean> => {
return initRuntimeSocket(hosts, port, id).then((socket): boolean => {
if (socket == null) {
return false
}
socketTask = socket
return true
})
})
.catch((): boolean => {
return false
})
}
initRuntimeSocketService()
@@ -0,0 +1,61 @@
/// <reference types="@dcloudio/uni-app-x/types/uni/global" />
// 之所以又写了一份,是因为外层的socket,connectSocket的时候必须传入multiple:true
// 但是android又不能传入,目前代码里又不能写条件编译之类的。
export function initRuntimeSocket(
hosts: string,
port: string,
id: string
): Promise<SocketTask | null> {
if (hosts == '' || port == '' || id == '') return Promise.resolve(null)
return hosts
.split(',')
.reduce<Promise<SocketTask | null>>(
(
promise: Promise<SocketTask | null>,
host: string
): Promise<SocketTask | null> => {
return promise.then((socket): Promise<SocketTask | null> => {
if (socket != null) return Promise.resolve(socket)
return tryConnectSocket(host, port, id)
})
},
Promise.resolve(null)
)
}
const SOCKET_TIMEOUT = 500
function tryConnectSocket(
host: string,
port: string,
id: string
): Promise<SocketTask | null> {
return new Promise((resolve, reject) => {
const socket = uni.connectSocket({
url: `ws://${host}:${port}/${id}`,
fail() {
resolve(null)
},
})
const timer = setTimeout(() => {
// @ts-expect-error
socket.close({
code: 1006,
reason: 'connect timeout',
} as CloseSocketOptions)
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)
})
})
}
@@ -0,0 +1,426 @@
import type { ComponentInternalInstance, ComponentPublicInstance } from 'vue'
import type { MessageType } from './utils'
interface NormalizeResult {
name?: string
type: string
subType?: string
className?: string
description?: string
value?: any
}
interface ObjectResultValue {
properties: Array<NormalizeResult>
}
interface ObjectResult extends NormalizeResult {
value: ObjectResultValue
}
interface ArrayResultValue {
properties: Array<NormalizeResult>
}
interface ArrayResult extends NormalizeResult {
value: ArrayResultValue
}
interface SetResultEntry {
value: NormalizeResult
}
interface SetResultValue {
entries: Array<SetResultEntry>
}
interface SetResult extends NormalizeResult {
value: SetResultValue
}
interface MapResultEntry {
key: NormalizeResult
value: NormalizeResult
}
interface MapResultValue {
entries: Array<MapResultEntry>
}
interface MapResult extends NormalizeResult {
value: MapResultValue
}
export interface Message {
type: MessageType
args: Array<any>
}
export function formatMessage(
type: MessageType,
args: Array<any | null>
): Message {
try {
return {
type,
args: formatArgs(args),
}
} catch (e) {
// originalConsole.error(e)
}
return {
type,
args: [],
}
}
export function formatArgs(args: Array<any | null>) {
return args.map((arg) => formatArg(arg))
}
export function formatArg(arg: any | null, depth: number = 0): NormalizeResult {
if (depth >= 7) {
return {
type: 'object',
value: '[Maximum depth reached]',
}
}
const type = typeof arg
switch (type) {
case 'string':
return formatString(arg as string)
case 'number':
return formatNumber(arg as number)
case 'boolean':
return formatBoolean(arg as boolean)
case 'object':
try {
// 鸿蒙里边 object 可能包含 nativePtr 指针,该指针 typeof 是 object
// 但是又不能访问上边的任意属性,否则会报:TypeError: Can not get Prototype on non ECMA Object
// 所以这里需要捕获异常,防止报错
return formatObject(arg as object, depth)
} catch (e) {
return {
type: 'object',
value: {
properties: [],
},
}
}
case 'undefined':
return formatUndefined()
case 'function':
return formatFunction(arg as Function)
case 'symbol':
if (__HARMONY__) {
return formatUnknown('symbol', arg as unknown)
} else {
return formatSymbol(arg as symbol)
}
case 'bigint':
return formatBigInt(arg as unknown)
}
}
function formatFunction(value: Function): NormalizeResult {
return {
type: 'function',
value: `function ${value.name}() {}`,
}
}
function formatUndefined(): NormalizeResult {
return {
type: 'undefined',
}
}
function formatBoolean(value: boolean): NormalizeResult {
return {
type: 'boolean',
value: String(value),
}
}
function formatNumber(value: number): NormalizeResult {
return {
type: 'number',
value: String(value),
}
}
function formatBigInt(value: unknown): NormalizeResult {
return {
type: 'bigint',
value: String(value),
}
}
function formatString(value: string): NormalizeResult {
return {
type: 'string',
value,
}
}
function formatSymbol(value: symbol): NormalizeResult {
return {
type: 'symbol',
value: value.description,
}
}
function formatUnknown(type: string, value: unknown): NormalizeResult {
return {
type,
value: String(value),
}
}
function formatObject(value: object, depth: number): NormalizeResult {
if (value === null) {
return {
type: 'null',
}
}
if (!__HARMONY__) {
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: any | null, i: number): NormalizeResult =>
formatArrayElement(v, i, depth + 1)
),
},
} as ArrayResult
}
if (value instanceof Set) {
return {
type: 'object',
subType: 'set',
className: 'Set',
description: `Set(${value.size})`,
value: {
entries: Array.from(value).map(
(v: any | null): SetResultEntry => formatSetEntry(v, depth + 1)
),
},
} as SetResult
}
if (value instanceof Map) {
return {
type: 'object',
subType: 'map',
className: 'Map',
description: `Map(${value.size})`,
value: {
entries: Array.from(value.entries()).map(
(v: Array<any | null>): MapResultEntry => formatMapEntry(v, depth + 1)
),
},
} as MapResult
}
if (value instanceof Promise) {
return {
type: 'object',
subType: 'promise',
value: {
properties: [],
},
} as ObjectResult
}
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: string | undefined = undefined
if (!__HARMONY__) {
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: [string, any | null]): NormalizeResult =>
formatObjectProperty(entry[0], entry[1], depth + 1)
),
},
} as ObjectResult
}
function isHarmonyBuilderParams(value: any) {
return value.modifier && value.modifier._attribute && value.nodeContent
}
function isComponentPublicInstance(
value: any
): value is ComponentPublicInstance {
return value.$ && isComponentInternalInstance(value.$)
}
function isComponentInternalInstance(
value: any
): value is ComponentInternalInstance {
return value.type && value.uid != null && value.appContext
}
function formatComponentPublicInstance(
value: ComponentPublicInstance,
depth: number
) {
return {
type: 'object',
className: 'ComponentPublicInstance',
value: {
properties: Object.entries(value.$.type).map(
([name, value]): NormalizeResult =>
formatObjectProperty(name, value, depth + 1)
),
},
}
}
function formatComponentInternalInstance(
value: ComponentInternalInstance,
depth: number
) {
return {
type: 'object',
className: 'ComponentInternalInstance',
value: {
properties: Object.entries(value.type).map(
([name, value]): NormalizeResult =>
formatObjectProperty(name, value, depth + 1)
),
},
}
}
function isUniElement(value: any): value is UniElement {
return value.style && value.tagName != null && value.nodeName != null
}
function formatUniElement(value: UniElement, depth: number) {
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]): NormalizeResult =>
formatObjectProperty(name, value, depth + 1)
),
},
}
}
function isCSSStyleDeclaration(
value: any
): value is CSSStyleDeclaration & { $styles: Record<string, string | null> } {
return (
typeof value.getPropertyValue === 'function' &&
typeof value.setProperty === 'function' &&
value.$styles
)
}
function formatCSSStyleDeclaration(
style: CSSStyleDeclaration & { $styles: Record<string, string | null> },
depth: number
) {
return {
type: 'object',
value: {
properties: Object.entries(style.$styles).map(([name, value]) =>
formatObjectProperty(name, value, depth + 1)
),
},
}
}
function formatObjectProperty(name: string, value: any | null, depth: number) {
const result = formatArg(value, depth)
result.name = name
return result
}
function formatArrayElement(value: any | null, index: number, depth: number) {
const result = formatArg(value, depth)
result.name = `${index}`
return result
}
function formatSetEntry(value: any | null, depth: number): SetResultEntry {
return {
value: formatArg(value, depth),
}
}
function formatMapEntry(
value: Array<any | null>,
depth: number
): MapResultEntry {
return {
key: formatArg(value[0], depth),
value: formatArg(value[1], depth),
}
}
@@ -0,0 +1,141 @@
import { sendErrorMessages } from '../error'
import type { SendFn } from '../utils'
import { type Message, formatMessage } from './format'
import { CONSOLE_TYPES, type MessageType, originalConsole } from './utils'
let sendConsole: SendFn = null
const messageQueue: Message[] = []
const messageExtra: Record<string, any> = {}
const EXCEPTION_BEGIN_MARK = '---BEGIN:EXCEPTION---'
const EXCEPTION_END_MARK = '---END:EXCEPTION---'
function sendConsoleMessages(messages: Message[]) {
if (sendConsole == null) {
messageQueue.push(...messages)
return
}
sendConsole(
JSON.stringify(
Object.assign(
{
type: 'console',
data: messages,
},
messageExtra
)
)
)
}
export function setSendConsole(value: SendFn, extra: Record<string, any> = {}) {
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+$/
export function rewriteConsole() {
if (__HARMONY_JSVM__) {
if (
typeof UTSProxyObject === 'object' &&
UTSProxyObject !== null &&
typeof UTSProxyObject.invokeSync === 'function'
) {
UTSProxyObject.invokeSync('__UniConsole', 'setSendConsoleMessages', [
sendConsoleMessages,
])
}
}
function wrapConsole(type: MessageType) {
return function (...args: any[]) {
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 (!process.env.UNI_CONSOLE_WEBVIEW) {
if (typeof uni !== 'undefined' && uni.__f__) {
const oldLog = uni.__f__
if (oldLog) {
// 重写 uni.__f__ 方法,这样的话,仅能打印开发者代码里的日志,其他没有被重写为__f__的日志将无法打印(比如uni-app框架、小程序框架等)
uni.__f__ = function (...args: any[]) {
const [type, filename, ...rest] = args
// 原始日志移除 filename
oldLog(type, '', ...rest)
sendConsoleMessages([formatMessage(type, [...rest, filename])])
}
return function restoreConsole() {
uni.__f__ = oldLog
}
}
}
}
}
return function restoreConsole() {
if (__HARMONY_JSVM__) {
if (
typeof UTSProxyObject === 'object' &&
UTSProxyObject !== null &&
typeof UTSProxyObject.invokeSync === 'function'
) {
UTSProxyObject.invokeSync('__UniConsole', '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
}
@@ -0,0 +1,11 @@
export const CONSOLE_TYPES = ['log', 'warn', 'error', 'info', 'debug'] as const
export type MessageType = 'log' | 'warn' | 'error' | 'info' | 'debug'
export const originalConsole = /*@__PURE__*/ CONSOLE_TYPES.reduce(
(methods, type) => {
methods[type] = console[type].bind(console)
return methods
},
{} as Record<MessageType, typeof console.log>
)
+114
View File
@@ -0,0 +1,114 @@
import { originalConsole } from './console/utils'
import type { SendFn } from './utils'
let sendError: SendFn = 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: Set<any> = new Set()
const errorExtra: Record<string, any> = {}
export function sendErrorMessages(errors: any[]) {
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
)
)
)
}
}
export function setSendError(value: SendFn, extra: Record<string, any> = {}) {
sendError = value
Object.assign(errorExtra, extra)
if (value != null && errorQueue.size > 0) {
const errors = Array.from(errorQueue)
errorQueue.clear()
sendErrorMessages(errors)
}
}
export function initOnError() {
function onError(error: any) {
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)
}
}
}
+7
View File
@@ -0,0 +1,7 @@
import { formatMessage } from './console/format'
import type { MessageType } from './console/utils'
export function __f__(type: MessageType, filename: string, ...args: any[]) {
const message = formatMessage(type, [...args, filename])
return message
}
+123
View File
@@ -0,0 +1,123 @@
import { initRuntimeSocket } from './socket'
import { rewriteConsole, setSendConsole } from './console'
import { initOnError, setSendError } from './error'
import { originalConsole } from './console/utils'
export function initRuntimeSocketService(): Promise<boolean> {
const hosts: string = process.env.UNI_SOCKET_HOSTS
const port: string = process.env.UNI_SOCKET_PORT
const id: string = 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
if (__PLATFORM__ === 'mp') {
originalConsole.error(
wrapError('小程序平台,请勾选不校验合法域名配置。')
)
}
originalConsole.error(
wrapError('如果是运行到真机,请确认手机与电脑处于同一网络。')
)
return false
}
// @ts-expect-error
if (__PLATFORM__ === 'mp') {
initMiniProgramGlobalFlag()
}
socket.onClose(() => {
if (process.env.UNI_DEBUG) {
originalConsole.log(
`uni-app:[${Date.now()}][socket]`,
'connect close and restore'
)
}
// @ts-expect-error
if (__PLATFORM__ === 'mp') {
originalConsole.error(
wrapError(
'开发模式下日志通道 socket 连接关闭,请在 HBuilderX 中重新运行。'
)
)
} else {
originalConsole.error(
wrapError(
'手机端日志通道 socket 连接已断开,请重启基座应用或重新运行。'
)
)
}
restoreError()
restoreConsole()
})
setSendConsole((data: string) => {
if (process.env.UNI_DEBUG) {
originalConsole.log(`uni-app:[${Date.now()}][console]`, data)
}
socket!.send({
data,
})
})
setSendError((data: string) => {
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: string) {
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()
+61
View File
@@ -0,0 +1,61 @@
/// <reference types="@dcloudio/uni-app-x/types/uni/global" />
export function initRuntimeSocket(
hosts: string,
port: string,
id: string
): Promise<SocketTask | null> {
if (hosts == '' || port == '' || id == '') return Promise.resolve(null)
return hosts
.split(',')
.reduce<Promise<SocketTask | null>>(
(
promise: Promise<SocketTask | null>,
host: string
): Promise<SocketTask | null> => {
return promise.then((socket): Promise<SocketTask | null> => {
if (socket != null) return Promise.resolve(socket)
return tryConnectSocket(host, port, id)
})
},
Promise.resolve(null)
)
}
const SOCKET_TIMEOUT = 500
function tryConnectSocket(
host: string,
port: string,
id: string
): Promise<SocketTask | null> {
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',
} as CloseSocketOptions)
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)
})
})
}
+1
View File
@@ -0,0 +1 @@
export type SendFn = ((msg: string) => void) | null
@@ -0,0 +1,66 @@
import { rewriteConsole, setSendConsole } from '../console'
import { sendErrorMessages, setSendError } from '../error'
declare global {
interface Window {
__UNI_CONSOLE_WEBVIEW__: boolean
__UNI_PAGE_ROUTE__: string
}
}
function initUniWebviewRuntimeService() {
if (window.__UNI_CONSOLE_WEBVIEW__) return
window.__UNI_CONSOLE_WEBVIEW__ = true
const channel = `[web-view]${
window.__UNI_PAGE_ROUTE__ ? `[${window.__UNI_PAGE_ROUTE__}]` : ''
}`
rewriteConsole()
setSendConsole(
(data: string) => {
sendToService(data)
},
{
channel,
}
)
setSendError(
(data: string) => {
sendToService(data)
},
{
channel,
}
)
// 监听同步错误
window.addEventListener('error', (event) => {
sendErrorMessages([event.error])
})
// 监听Promise未处理的异步错误
window.addEventListener('unhandledrejection', (event) => {
sendErrorMessages([event])
})
}
function sendToService(data: string) {
// 发送数据到 service 层
const serviceMessage = {
type: 'WEB_INVOKE_APPSERVICE',
args: {
data: {
name: 'console',
arg: data,
},
},
}
// @ts-expect-error
if (window.__uniapp_x_postMessageToService) {
// @ts-expect-error
return window.__uniapp_x_postMessageToService(serviceMessage)
} else {
// @ts-expect-error
return window.__uniapp_x_.postMessageToService(
JSON.stringify(serviceMessage)
)
}
}
initUniWebviewRuntimeService()