312 lines
13 KiB
Bash
Executable File
312 lines
13 KiB
Bash
Executable File
#!/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
|