Compare commits

..

3 Commits

6 changed files with 225 additions and 16 deletions
+37
View File
@@ -1359,6 +1359,34 @@ function getRendererConsoleLogPath() {
}
}
function getRendererDebugLogPath() {
try {
const dir = app.getPath("userData");
fs.mkdirSync(dir, { recursive: true });
return path.join(dir, "renderer-debug.log");
} catch {
return null;
}
}
function appendRendererDebugLog(line) {
const logPath = getRendererDebugLogPath();
if (!logPath) return;
try {
fs.appendFileSync(logPath, line, { encoding: "utf8" });
} catch {}
}
function stringifyDebugDetails(details) {
if (details == null) return "";
if (typeof details === "string") return details;
try {
return JSON.stringify(details);
} catch (err) {
return `[unserializable:${err?.message || err}]`;
}
}
function setupRendererConsoleLogging(win) {
if (!debugEnabled()) return;
@@ -1575,6 +1603,15 @@ function registerWindowIpc() {
}
});
ipcMain.on("debug:log", (event, payload) => {
const scope = String(payload?.scope || "renderer").trim() || "renderer";
const message = String(payload?.message || "").trim() || "(empty)";
const url = String(payload?.url || event?.sender?.getURL?.() || "").trim();
const details = stringifyDebugDetails(payload?.details);
const suffix = details ? ` details=${details}` : "";
appendRendererDebugLog(`[${nowIso()}] [${scope}] ${message} url=${url}${suffix}\n`);
});
ipcMain.handle("app:setCloseBehavior", (_event, behavior) => {
try {
const next = setCloseBehavior(behavior);
+61
View File
@@ -1,5 +1,65 @@
const { contextBridge, ipcRenderer } = require("electron");
function sendDebugLog(scope, message, details) {
try {
ipcRenderer.send("debug:log", {
scope: String(scope || "renderer"),
message: String(message || ""),
details: details == null ? {} : details,
url: typeof location !== "undefined" ? String(location.href || "") : "",
});
} catch {}
}
sendDebugLog("preload", "script-start", {
userAgent: typeof navigator !== "undefined" ? String(navigator.userAgent || "") : "",
});
if (typeof document !== "undefined") {
document.addEventListener("readystatechange", () => {
sendDebugLog("preload", "document-readystate", {
readyState: String(document.readyState || ""),
});
});
}
if (typeof window !== "undefined") {
window.addEventListener("DOMContentLoaded", () => {
sendDebugLog("preload", "dom-content-loaded");
});
window.addEventListener("load", () => {
sendDebugLog("preload", "window-load");
});
window.addEventListener("error", (event) => {
sendDebugLog("preload", "window-error", {
message: String(event?.message || ""),
filename: String(event?.filename || ""),
lineno: Number(event?.lineno || 0),
colno: Number(event?.colno || 0),
});
});
window.addEventListener("unhandledrejection", (event) => {
const reason = event?.reason;
sendDebugLog("preload", "window-unhandledrejection", {
reason:
reason instanceof Error
? {
name: String(reason.name || "Error"),
message: String(reason.message || ""),
stack: String(reason.stack || ""),
}
: String(reason || ""),
});
});
window.setTimeout(() => {
sendDebugLog("preload", "set-timeout-0");
}, 0);
}
contextBridge.exposeInMainWorld("wechatDesktop", {
// Marker used by the frontend to distinguish the Electron desktop shell from the pure web build.
__brand: "WeChatDataAnalysisDesktop",
@@ -8,6 +68,7 @@ contextBridge.exposeInMainWorld("wechatDesktop", {
close: () => ipcRenderer.invoke("window:close"),
isMaximized: () => ipcRenderer.invoke("window:isMaximized"),
isDebugEnabled: () => ipcRenderer.invoke("app:isDebugEnabled"),
logDebug: (scope, message, details = {}) => sendDebugLog(scope, message, details),
getAutoLaunch: () => ipcRenderer.invoke("app:getAutoLaunch"),
setAutoLaunch: (enabled) => ipcRenderer.invoke("app:setAutoLaunch", !!enabled),
+10 -8
View File
@@ -1,5 +1,5 @@
<script>
import { defineComponent, h, ref } from 'vue'
import { defineComponent, h, ref, watch } from 'vue'
import miniProgramIconUrl from '~/assets/images/wechat/mini-program.svg'
export default defineComponent({
@@ -19,7 +19,15 @@ export default defineComponent({
setup(props) {
const fromAvatarImgOk = ref(false)
const fromAvatarImgError = ref(false)
const lastFromAvatarUrl = ref('')
watch(
() => String(props.fromAvatar || '').trim(),
() => {
fromAvatarImgOk.value = false
fromAvatarImgError.value = false
},
{ immediate: true }
)
const getFromText = () => {
const raw = String(props.from || '').trim()
@@ -47,12 +55,6 @@ export default defineComponent({
const isCoverVariant = !isMiniProgram && String(props.variant || '').trim() === 'cover'
const Tag = canNavigate ? 'a' : 'div'
if (fromAvatarUrl !== lastFromAvatarUrl.value) {
lastFromAvatarUrl.value = fromAvatarUrl
fromAvatarImgOk.value = false
fromAvatarImgError.value = false
}
const showFromAvatarImg = Boolean(fromAvatarUrl) && !fromAvatarImgError.value
const showFromAvatarText = (!fromAvatarUrl) || (!fromAvatarImgOk.value)
const fromAvatarStyle = fromAvatarImgOk.value
+31 -8
View File
@@ -36,6 +36,9 @@ export const useChatMessages = ({
const logMessagePhase = (phase, details = {}) => {
if (!isDesktopRenderer()) return
try {
window.wechatDesktop?.logDebug?.('chat-messages', phase, details)
} catch {}
console.info(`[chat-messages] ${phase}`, {
account: String(selectedAccount.value || '').trim(),
selectedUsername: String(selectedContact.value?.username || '').trim(),
@@ -44,12 +47,21 @@ export const useChatMessages = ({
})
}
const summarizeRenderTypes = (list) => {
const counts = {}
for (const item of Array.isArray(list) ? list : []) {
const key = String(item?.renderType || 'unknown').trim() || 'unknown'
counts[key] = Number(counts[key] || 0) + 1
}
return counts
}
const previewImageUrl = ref(null)
const previewVideoUrl = ref(null)
const previewVideoPosterUrl = ref('')
const previewVideoError = ref('')
const voiceRefs = ref({})
const voiceRefs = new Map()
const currentPlayingVoice = ref(null)
const playingVoiceId = ref(null)
@@ -227,18 +239,16 @@ export const useChatMessages = ({
const key = String(id || '').trim()
if (!key) return
if (element) {
voiceRefs.value = { ...voiceRefs.value, [key]: element }
} else if (voiceRefs.value[key]) {
const next = { ...voiceRefs.value }
delete next[key]
voiceRefs.value = next
voiceRefs.set(key, element)
} else {
voiceRefs.delete(key)
}
}
const playVoiceById = async (voiceId) => {
const key = String(voiceId || '').trim()
if (!key) return
const audio = voiceRefs.value[key]
const audio = voiceRefs.get(key)
if (!audio) return
try {
@@ -418,7 +428,8 @@ export const useChatMessages = ({
const mapped = dedupeMessagesById(raw.map(normalizeMessage))
logMessagePhase('loadMessages:normalize:end', {
username,
mappedCount: mapped.length
mappedCount: mapped.length,
renderTypeCounts: summarizeRenderTypes(mapped)
})
if (activeMessagesFor.value !== username) {
@@ -595,7 +606,18 @@ export const useChatMessages = ({
} catch {}
}
const clearVoicePlaybackState = () => {
try {
currentPlayingVoice.value?.pause?.()
if (currentPlayingVoice.value) currentPlayingVoice.value.currentTime = 0
} catch {}
currentPlayingVoice.value = null
playingVoiceId.value = null
voiceRefs.clear()
}
const resetMessageState = () => {
clearVoicePlaybackState()
allMessages.value = {}
messagesMeta.value = {}
messagesError.value = ''
@@ -804,6 +826,7 @@ export const useChatMessages = ({
if (highlightTimer) clearTimeout(highlightTimer)
highlightTimer = null
clearContactProfileHoverHideTimer()
clearVoicePlaybackState()
})
return {
+3
View File
@@ -94,6 +94,9 @@ const shouldLogChatBootstrap = () => isDesktopShell() || desktopDebugEnabled.val
const logChatBootstrap = (phase, details = {}) => {
if (!shouldLogChatBootstrap()) return
try {
window.wechatDesktop?.logDebug?.('chat-bootstrap', phase, details)
} catch {}
console.info(`[chat-bootstrap] ${phase}`, {
elapsedMs: chatBootstrapElapsedMs(),
route: route.fullPath,
+83
View File
@@ -0,0 +1,83 @@
const isDesktopShell = () => {
if (typeof window === 'undefined') return false
return !!window.wechatDesktop?.__brand
}
const formatError = (error) => {
if (!error) return ''
if (error instanceof Error) {
return {
name: String(error.name || 'Error'),
message: String(error.message || ''),
stack: String(error.stack || '')
}
}
if (typeof error === 'object') {
try {
return JSON.parse(JSON.stringify(error))
} catch {}
}
return String(error)
}
const logDesktopDebug = (phase, details = {}) => {
if (!isDesktopShell()) return
try {
window.wechatDesktop?.logDebug?.('nuxt-bootstrap', phase, {
href: String(window.location?.href || ''),
...details
})
} catch {}
try {
console.info(`[nuxt-bootstrap] ${phase}`, details)
} catch {}
}
export default defineNuxtPlugin((nuxtApp) => {
logDesktopDebug('plugin:setup')
if (typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
logDesktopDebug('window:error', {
message: String(event?.message || ''),
filename: String(event?.filename || ''),
lineno: Number(event?.lineno || 0),
colno: Number(event?.colno || 0),
error: formatError(event?.error)
})
})
window.addEventListener('unhandledrejection', (event) => {
logDesktopDebug('window:unhandledrejection', {
reason: formatError(event?.reason)
})
})
}
nuxtApp.hook('app:created', () => {
logDesktopDebug('app:created')
})
nuxtApp.hook('app:beforeMount', () => {
logDesktopDebug('app:beforeMount')
})
nuxtApp.hook('app:mounted', () => {
logDesktopDebug('app:mounted')
})
nuxtApp.hook('page:start', () => {
logDesktopDebug('page:start')
})
nuxtApp.hook('page:finish', () => {
logDesktopDebug('page:finish')
})
nuxtApp.hook('vue:error', (error, _instance, info) => {
logDesktopDebug('vue:error', {
info: String(info || ''),
error: formatError(error)
})
})
})