#!/usr/bin/env bash set -euo pipefail cat <<'EOF' 在 Chrome DevTools Console 粘贴执行: (() => { const stamp = () => new Date().toISOString(); const counts = {}; if (!window.__deployTestWorkerWrapped) { const NativeWorker = window.Worker; window.Worker = function (url, options) { console.log(`[probe ${stamp()}] new Worker`, { url: String(url), options }); const worker = new NativeWorker(url, options); worker.addEventListener("error", (event) => { console.error("[probe worker error]", { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, }); }); worker.addEventListener("messageerror", (event) => { console.error("[probe worker messageerror]", event); }); worker.addEventListener("message", (event) => { console.log(`[probe ${stamp()}] worker.message`, { url: String(url), data: event.data }); }); const originalPostMessage = worker.postMessage.bind(worker); worker.postMessage = (...args) => { console.log(`[probe ${stamp()}] worker.postMessage`, { url: String(url), args }); return originalPostMessage(...args); }; return worker; }; window.Worker.prototype = NativeWorker.prototype; window.__deployTestWorkerWrapped = true; console.log("[probe] Worker: wrapped; reload page after installing this probe to catch SDK worker creation and __absurd:spawn-idb-worker"); } const wrap = (name) => { const original = window[name]; if (typeof original !== "function") { console.log(`[probe] ${name}:`, typeof original); return; } counts[name] = 0; const wrapped = function (...args) { counts[name] += 1; console.log(`[probe ${stamp()}] ${name} called`, args); try { const ret = original.apply(this, args); console.log(`[probe ${stamp()}] ${name} returned`, ret); return ret; } catch (err) { console.error(`[probe ${stamp()}] ${name} threw`, err); throw err; } }; wrapped.__deployTestProbe = true; wrapped.__deployTestOriginal = original; window[name] = wrapped; console.log(`[probe] ${name}: wrapped`); }; ["initSDK", "login", "logout", "commonEventFunc", "initDB", "setSqlWasmPath"].forEach(wrap); window.addEventListener("error", (event) => { console.error("[probe window error]", event.message, event.filename, event.lineno, event.error); }); window.addEventListener("unhandledrejection", (event) => { console.error("[probe unhandledrejection]", event.reason); }); console.log("[probe ready]", { electronAPI: Boolean(window.electronAPI), href: window.location.href, isSecureContext: window.isSecureContext, crossOriginIsolated: window.crossOriginIsolated, SharedArrayBuffer: typeof window.SharedArrayBuffer, Go: typeof Go, initSDK: typeof window.initSDK, login: typeof window.login, logout: typeof window.logout, commonEventFunc: typeof window.commonEventFunc, initDB: typeof window.initDB, setSqlWasmPath: typeof window.setSqlWasmPath, openIMRenderApi: typeof window.openIMRenderApi, }); window.__deployTestProbeCounts = counts; window.__deployTestReadLocalForage = async (key) => { const tryRead = (dbName, storeName) => new Promise((resolve, reject) => { const req = indexedDB.open(dbName); req.onerror = () => reject(req.error); req.onsuccess = () => { const db = req.result; if (!Array.from(db.objectStoreNames || []).includes(storeName)) { db.close(); resolve(undefined); return; } const tx = db.transaction(storeName, "readonly"); const store = tx.objectStore(storeName); const getReq = store.get(key); getReq.onerror = () => reject(getReq.error); getReq.onsuccess = () => { db.close(); resolve(getReq.result); }; }; }); for (const dbName of ["localforage", "OpenCorp-Base"]) { for (const storeName of ["keyvaluepairs", "local-forage-detect-blob-support"]) { try { const value = await tryRead(dbName, storeName); if (value) return value; } catch (err) { console.warn(`[probe] read ${dbName}/${storeName}/${key} failed`, err); } } } return undefined; }; window.__deployTestAuthState = async () => { const state = { href: window.location.href, hash: window.location.hash, isSecureContext: window.isSecureContext, crossOriginIsolated: window.crossOriginIsolated, SharedArrayBuffer: typeof window.SharedArrayBuffer, electronAPI: Boolean(window.electronAPI), Go: typeof Go, initSDK: typeof window.initSDK, login: typeof window.login, logout: typeof window.logout, initDB: typeof window.initDB, setSqlWasmPath: typeof window.setSqlWasmPath, userID: await window.__deployTestReadLocalForage("IM_USERID"), hasIMToken: Boolean(await window.__deployTestReadLocalForage("IM_TOKEN")), hasChatToken: Boolean(await window.__deployTestReadLocalForage("IM_CHAT_TOKEN")), counts: { ...counts }, }; console.log("[probe] auth state", state); console.log("[probe] auth state json", JSON.stringify(state, null, 2)); return state; }; if (!window.__deployTestXHRWrapped) { const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...args) { this.__deployTestRequest = { method, url, startedAt: Date.now() }; return originalOpen.call(this, method, url, ...args); }; XMLHttpRequest.prototype.send = function (...args) { const req = this.__deployTestRequest; if (req) { this.addEventListener("loadend", () => { if (String(req.url).includes("/account/login") || String(req.url).includes("/auth/get_user_token")) { console.log("[probe xhr]", { method: req.method, url: req.url, status: this.status, ms: Date.now() - req.startedAt, response: String(this.responseText || "").slice(0, 500), }); } }); } return originalSend.apply(this, args); }; window.__deployTestXHRWrapped = true; console.log("[probe] XMLHttpRequest: wrapped"); } window.__deployTestListBrowserDBs = async () => { const dbs = indexedDB.databases ? await indexedDB.databases() : []; console.log("[probe] indexedDB databases", dbs); if (window.caches?.keys) { console.log("[probe] cache keys", await caches.keys()); } return dbs; }; window.__deployTestResetBrowserStorage = async () => { console.warn("[probe] 清理当前站点浏览器存储:localStorage / CacheStorage / IndexedDB"); localStorage.clear(); if (window.caches?.keys) { for (const key of await caches.keys()) { console.warn("[probe] delete cache", key, await caches.delete(key)); } } if (indexedDB.databases) { for (const db of await indexedDB.databases()) { if (!db.name) continue; await new Promise((resolve) => { const req = indexedDB.deleteDatabase(db.name); req.onsuccess = () => { console.warn("[probe] deleted indexedDB", db.name); resolve(); }; req.onerror = () => { console.warn("[probe] delete indexedDB failed", db.name, req.error); resolve(); }; req.onblocked = () => { console.warn("[probe] delete indexedDB blocked, close other tabs then retry", db.name); resolve(); }; }); } } else { console.warn("[probe] indexedDB.databases() 不可用,请在 DevTools Application 面板手动清理 IndexedDB"); } console.warn("[probe] 清理完成,请关闭其它同源标签页后刷新页面并重新登录"); }; window.__deployTestManualLogin = async ({ userID, token, apiAddr, wsAddr, dataDir = "./" } = {}) => { userID = userID || await window.__deployTestReadLocalForage("IM_USERID"); token = token || await window.__deployTestReadLocalForage("IM_TOKEN"); const operationID = `deploy-test-${Date.now()}`; const gatewayOrigin = window.location.origin || "https://pc-jack.imharry.work"; const config = { platformID: 5, apiAddr: apiAddr || `${gatewayOrigin}/api/im`, wsAddr: wsAddr || `${gatewayOrigin.replace(/^http/, "ws")}/msg_gateway`, dataDir: String(dataDir), logLevel: 5, isLogStandardOutput: true, logFilePath: "./", isExternalExtensions: false, }; console.log(`[probe ${stamp()}] manual initSDK`, { operationID, config }); window.initSDK(operationID, JSON.stringify(config)); console.log(`[probe ${stamp()}] manual login start`, { operationID, userID, hasToken: Boolean(token) }); const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error("manual window.login timeout after 15000ms")), 15000); }); const result = await Promise.race([window.login(operationID, String(userID || ""), String(token || "")), timeout]); console.log(`[probe ${stamp()}] manual login result`, result); return result; }; window.__deployTestManualInitDB = async (userID, dataDir = "./") => { userID = userID || await window.__deployTestReadLocalForage("IM_USERID") || "6087132211"; console.log(`[probe ${stamp()}] manual setSqlWasmPath`, "/sql-wasm.wasm"); if (typeof window.setSqlWasmPath === "function") { try { const setPathTimeout = new Promise((_, reject) => { setTimeout(() => reject(new Error("manual window.setSqlWasmPath timeout after 5000ms")), 5000); }); await Promise.race([window.setSqlWasmPath("/sql-wasm.wasm"), setPathTimeout]); console.log(`[probe ${stamp()}] manual setSqlWasmPath done`); } catch (err) { console.error(`[probe ${stamp()}] manual setSqlWasmPath failed`, err); } } console.log(`[probe ${stamp()}] manual initDB start`, { userID, dataDir }); const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error("manual window.initDB timeout after 15000ms")), 15000); }); const result = await Promise.race([window.initDB(String(userID), String(dataDir)), timeout]); console.log(`[probe ${stamp()}] manual initDB result`, result); return result; }; clearInterval(window.__deployTestProbeTimer); window.__deployTestProbeTimer = setInterval(() => { const snapshot = { ...counts }; console.log(`[probe ${stamp()}] counts`, snapshot); if (!snapshot.initSDK && !snapshot.login) { console.warn("[probe hint] initSDK/login 仍为 0:当前页面没有重新触发 SDK 登录。若 logout > 0 且报 10008,只说明业务层在未初始化 SDK 时触发了退出。请执行 await window.__deployTestAuthState();若仍有 token,可执行 await window.__deployTestManualLogin() 手动验证 SDK 初始化链路。"); } }, 5000); })(); 然后退出登录/刷新页面重新登录一次,观察是否打印: - new Worker / worker.postMessage / worker error - initSDK / login / logout / initDB / setSqlWasmPath called / returned - window error / unhandledrejection - counts 里的 initSDK/login 是否从 0 变成 1 如果点登录后 counts 仍为 0,可用当前登录 token 手动测: window.__deployTestManualLogin() 如果自动读取 token 失败,再手动传入: window.__deployTestManualLogin({ userID: "6087132211", token: "把 console 里 SDK login args 的 token 粘到这里" }) 若默认 dataDir="./" 仍报 initDB timeout,可验证是否是浏览器 IDBFS 路径问题: await window.__deployTestManualLogin({ dataDir: "" }) await window.__deployTestManualInitDB(undefined, "") 若报 10006 init database invoke javascript timeout,先查看并清理当前站点浏览器数据库: await window.__deployTestListBrowserDBs() await window.__deployTestResetBrowserStorage() 也可以单独测试 initDB: await window.__deployTestManualInitDB() 清理后按这个顺序重测: 1. 关闭其它 https://pc-jack.imharry.work/ 标签页 2. 打开 https://pc-jack.imharry.work/ 并刷新页面 3. 重新粘贴本脚本输出的整段 JS(刷新会清掉已安装的 probe) 4. 再登录 5. 观察 counts 是否从 0 变 1,以及 /var/log/nginx/openim-pc-proxy-access.log 是否出现 /msg_gateway 如果 counts 仍为 0,查看业务登录链路状态: await window.__deployTestAuthState() HTTPS 域名入口下 isSecureContext / crossOriginIsolated 应为 true,SharedArrayBuffer 应为 "function";同时观察 SDK 方法是否存在、login 是否返回成功,以及 /msg_gateway 是否建立连接。 EOF