cicy-desktop 2.1.364 → 2.1.366
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/inject/no-passkey.js +34 -32
- package/src/main.js +30 -0
package/package.json
CHANGED
package/src/inject/no-passkey.js
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
// Copyright 2026 CiCy AI
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
// document-start
|
|
4
|
+
// document-start 注入:让页面认为这台机器没有可用的通行密钥验证器,从而不弹
|
|
5
|
+
// Windows 的「请将安全密钥插入 USB 端口」系统模态框(会卡死矩阵里的自动登录)。
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// 照样是 function)。
|
|
13
|
-
//
|
|
14
|
-
// 为什么不直接 delete PublicKeyCredential:整个对象拿掉会让 Facebook 的两步验证页渲染不出来
|
|
15
|
-
// (Comet 挂载了但树是空的,实测白板)。所以对象保留,只回答「没有验证器」,并让 passkey 请求
|
|
16
|
-
// 以标准的 NotAllowedError(等同用户取消)拒绝 —— 站点对这个错误都有正常的密码回退路径。
|
|
7
|
+
// 关键:必须改**主世界**的 navigator.credentials / PublicKeyCredential。
|
|
8
|
+
// 这个文件既作普通窗口的 session 预加载,也作 <webview> 客机的 preload,两种场景
|
|
9
|
+
// 大多 contextIsolation=true —— 预加载跑在隔离世界,直接改 navigator 改的是隔离世界的副本,
|
|
10
|
+
// 页面(主世界)根本不受影响(实测 2.1.364:webview 里 credentials.get 仍是原生,照弹框)。
|
|
11
|
+
// 所以统一走「往文档里插一个 <script> 让补丁在主世界执行」,隔离/主世界都能生效,
|
|
12
|
+
// 且 document-start 时插入 = 先于页面脚本。
|
|
17
13
|
(function () {
|
|
14
|
+
const patch = function () {
|
|
15
|
+
try {
|
|
16
|
+
if (window.PublicKeyCredential) {
|
|
17
|
+
const no = function () { return Promise.resolve(false); };
|
|
18
|
+
try { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = no; } catch (e) {}
|
|
19
|
+
try { PublicKeyCredential.isConditionalMediationAvailable = no; } catch (e) {}
|
|
20
|
+
try { if (PublicKeyCredential.getClientCapabilities) PublicKeyCredential.getClientCapabilities = function () { return Promise.resolve({}); }; } catch (e) {}
|
|
21
|
+
}
|
|
22
|
+
if (navigator.credentials) {
|
|
23
|
+
const get = navigator.credentials.get ? navigator.credentials.get.bind(navigator.credentials) : null;
|
|
24
|
+
const create = navigator.credentials.create ? navigator.credentials.create.bind(navigator.credentials) : null;
|
|
25
|
+
const deny = function () { return Promise.reject(new DOMException("The operation either timed out or was not allowed.", "NotAllowedError")); };
|
|
26
|
+
if (get) navigator.credentials.get = function (o) { return (o && o.publicKey) ? deny() : get(o); };
|
|
27
|
+
if (create) navigator.credentials.create = function (o) { return (o && o.publicKey) ? deny() : create(o); };
|
|
28
|
+
}
|
|
29
|
+
} catch (e) { /* 不能影响页面 */ }
|
|
30
|
+
};
|
|
18
31
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
if (navigator.credentials) {
|
|
30
|
-
const get = navigator.credentials.get.bind(navigator.credentials);
|
|
31
|
-
const create = navigator.credentials.create ? navigator.credentials.create.bind(navigator.credentials) : null;
|
|
32
|
-
const deny = () => Promise.reject(
|
|
33
|
-
new DOMException("The operation either timed out or was not allowed.", "NotAllowedError"));
|
|
34
|
-
// 只挡 publicKey(通行密钥);密码管理器那套 credentials 不受影响。
|
|
35
|
-
navigator.credentials.get = (o) => (o && o.publicKey ? deny() : get(o));
|
|
36
|
-
if (create) navigator.credentials.create = (o) => (o && o.publicKey ? deny() : create(o));
|
|
37
|
-
}
|
|
38
|
-
} catch (e) { /* 注入失败不能影响页面 */ }
|
|
32
|
+
// 主世界执行:把补丁函数体塞进一个 <script>,插到 documentElement 上同步运行,随即移除。
|
|
33
|
+
const el = document.createElement("script");
|
|
34
|
+
el.textContent = "(" + patch.toString() + ")();";
|
|
35
|
+
(document.documentElement || document.head || document).appendChild(el);
|
|
36
|
+
el.remove();
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// 万一 DOM 还没有(极早期),退回直接在当前世界打一遍(contextIsolation=false 时这就够了)
|
|
39
|
+
try { patch(); } catch (e2) {}
|
|
40
|
+
}
|
|
39
41
|
})();
|
package/src/main.js
CHANGED
|
@@ -37,6 +37,36 @@ function armNoPasskey(ses) {
|
|
|
37
37
|
} catch (_) {}
|
|
38
38
|
}
|
|
39
39
|
electronApp.on("session-created", armNoPasskey);
|
|
40
|
+
// <webview> 客机不吃 session 级预加载(Electron 机制:那只对普通窗口/BrowserView 生效),
|
|
41
|
+
// 得在客机 attach 时把 no-passkey 设成它自己的 preload,否则矩阵格子(全是 webview)里
|
|
42
|
+
// 的 FB 页照样能调 navigator.credentials.get → Windows 弹安全密钥框(实测 2.1.364)。
|
|
43
|
+
// preload 内部用「插 <script> 改主世界」,所以不用动客机的 contextIsolation。
|
|
44
|
+
// 实测:will-attach-webview 里设 preload、以及 <webview> 元素 preload 属性,在这个
|
|
45
|
+
// Electron + 面板 BrowserView 组合下都被忽略(preload 根本不加载)。唯一可靠的、能在
|
|
46
|
+
// 文档创建前于主世界执行、赢过 FB 页首帧 credentials.get 的办法是 CDP 的
|
|
47
|
+
// Page.addScriptToEvaluateOnNewDocument。给每个 <webview> 客机挂上。
|
|
48
|
+
const NPK_SRC = "(function(){try{"
|
|
49
|
+
+ "if(window.PublicKeyCredential){"
|
|
50
|
+
+ "PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable=function(){return Promise.resolve(false)};"
|
|
51
|
+
+ "PublicKeyCredential.isConditionalMediationAvailable=function(){return Promise.resolve(false)};}"
|
|
52
|
+
+ "if(navigator.credentials&&navigator.credentials.get){"
|
|
53
|
+
+ "var g=navigator.credentials.get.bind(navigator.credentials);"
|
|
54
|
+
+ "navigator.credentials.get=function(o){return (o&&o.publicKey)?"
|
|
55
|
+
+ "Promise.reject(new DOMException('The operation either timed out or was not allowed.','NotAllowedError')):g(o)};}"
|
|
56
|
+
+ "}catch(e){}})();";
|
|
57
|
+
async function armWebviewNoPasskey(wc) {
|
|
58
|
+
try {
|
|
59
|
+
if (!wc || wc.isDestroyed() || wc.getType() !== "webview" || wc.__npkArmed) return;
|
|
60
|
+
wc.__npkArmed = true;
|
|
61
|
+
if (!wc.debugger.isAttached()) wc.debugger.attach("1.3");
|
|
62
|
+
await wc.debugger.sendCommand("Page.enable");
|
|
63
|
+
await wc.debugger.sendCommand("Page.addScriptToEvaluateOnNewDocument", { source: NPK_SRC });
|
|
64
|
+
try { await wc.executeJavaScript(NPK_SRC, true); } catch (_) {} // 当前已加载的文档也补一发
|
|
65
|
+
} catch (_) { try { wc.__npkArmed = false; } catch (e) {} }
|
|
66
|
+
}
|
|
67
|
+
electronApp.on("web-contents-created", (_e, wc) => {
|
|
68
|
+
try { if (wc.getType() === "webview") armWebviewNoPasskey(wc); } catch (_) {}
|
|
69
|
+
});
|
|
40
70
|
// 默认会话可能在上面这个监听器挂上之前就已经建好了(main.js 里别处也踩过同一个坑,
|
|
41
71
|
// 见启动时那段「默认会话强制直连」的保险),所以 ready 之后再补一次。幂等。
|
|
42
72
|
electronApp.whenReady().then(() => {
|